diff --git a/docs/upgrading/upgrade-to-v4.md b/docs/upgrading/upgrade-to-v4.md
new file mode 100644
index 0000000..eaf353c
--- /dev/null
+++ b/docs/upgrading/upgrade-to-v4.md
@@ -0,0 +1,197 @@
+
Upgrade to idn-area version 4
+
+## Changed response format
+
+Every endpoint now returns an object with the following properties:
+
+| Property | Type | Description | Available On |
+| --- | --- | --- | --- |
+| `statusCode` | `number` | HTTP status code | Always |
+| `message` | `string` or array of `string` | The message of the response | Always |
+| `error` | `string` | The kind of error | Error |
+| `data` | `object` or array of `object` | The data of the response | Success |
+| `meta` | `object` or `undefined` | The meta data of the response (optional) | Success |
+
+For example:
+
+- Get a province (success response)
+
+ ```
+ GET /provinces/32
+ ```
+ ```json
+ {
+ "statusCode": 200,
+ "message": "OK",
+ "data": {
+ "code": "32",
+ "name": "JAWA BARAT"
+ }
+ }
+ ```
+
+- Get provinces (success response)
+
+ ```
+ GET /provinces
+ ```
+ ```json
+ {
+ "statusCode": 200,
+ "message": "OK",
+ "data": [
+ {
+ "code": "11",
+ "name": "ACEH"
+ },
+ {
+ "code": "12",
+ "name": "SUMATERA UTARA"
+ },
+ ...
+ ],
+ "meta": {
+ "total": 10,
+ "pagination": {
+ "total": 37,
+ "pages": {
+ "first": 1,
+ "last": 2,
+ "current": 1,
+ "previous": null,
+ "next": 2
+ }
+ }
+ }
+ }
+ ```
+
+ > The endpoint above implements new pagination feature. See [pagination](#pagination) for details.
+
+- A bad request (error response)
+
+ ```
+ GET /provinces/ab
+ ```
+ ```json
+ {
+ "statusCode": 400,
+ "message": ["code must be a number string"],
+ "error": "Bad Request"
+ }
+ ```
+
+## Removed endpoints
+
+The following endpoints have been removed:
+
+- `GET /provinces/{code}/regencies`
+- `GET /regencies/{code}/districts`
+- `GET /regencies/{code}/islands`
+- `GET /districts/{code}/villages`
+
+You need to use the equivalent endpoints with the [`parentCode` query](#new-parentcode-query).
+
+> [!WARNING]
+> If you try to access the removed endpoints above, you will get a `404 Not Found` response.
+
+## New `parentCode` query
+
+The `parentCode` query parameter is added to the following endpoints:
+
+| Endpoint | `parentCode` Query | Example |
+| --- | --- | --- |
+| `GET /regencies` | `provinceCode` | `GET /regencies?provinceCode=32` |
+| `GET /districts` | `regencyCode` | `GET /districts?regencyCode=3201` |
+| `GET /islands` | `regencyCode` | `GET /islands?regencyCode=3201` |
+| `GET /villages` | `districtCode` | `GET /villages?districtCode=3201010` |
+
+This table below shows the [removed endpoints](#removed-endpoints) and its equivalent endpoints using the `parentCode` query.
+
+| Deleted Endpoint | Equivalent Endpoint |
+|--------|--------|
+| `GET /provinces/{code}/regencies` | **`GET /regencies?provinceCode={code}`** |
+| `GET /regencies/{code}/districts` | **`GET /districts?regencyCode={code}`** |
+| `GET /regencies/{code}/islands` | **`GET /islands?regencyCode={code}`** |
+| `GET /districts/{code}/villages` | **`GET /villages?districtCode={code}`** |
+
+Below is an example to get all regencies in province with code `32`:
+
+```
+GET /regencies?provinceCode=32
+```
+```json
+{
+ "statusCode": 200,
+ "message": "OK",
+ "data": [
+ {
+ "code": "3201",
+ "name": "KAB. BOGOR",
+ "provinceCode": "32"
+ },
+ {
+ "code": "3202",
+ "name": "KAB. SUKABUMI",
+ "provinceCode": "32"
+ },
+ ...
+ ],
+ "meta": {
+ "total": 10,
+ "pagination": {
+ "total": 27,
+ "pages": {
+ "first": 1,
+ "last": 3,
+ "current": 1,
+ "previous": null,
+ "next": 2
+ }
+ }
+ }
+}
+```
+
+> The endpoint above implements new pagination feature. See [pagination](#pagination) for details.
+
+## `name` query now optional
+
+Before version 4, the `name` query parameter is required, so you can't get all data without specifying the `name` query parameter.
+
+Now, the `name` query parameter is optional. This change affects the following endpoints:
+
+- `GET /regencies`
+- `GET /districts`
+- `GET /islands`
+- `GET /villages`
+
+## Pagination
+
+We introduce a new pagination feature. This feature is implemented in the following endpoints:
+
+- `GET /provinces`
+- `GET /regencies`
+- `GET /districts`
+- `GET /islands`
+- `GET /villages`
+
+You can use the **`page` and `limit`** query parameters to specify the page number and the number of data per page. If you don't specify the `page` and `limit` query parameters, the default value will be used (`page=1` and `limit=10`).
+
+```
+GET /provinces?page=2&limit=37
+```
+
+The response will contain a `meta` object with the following properties:
+
+| Property | Type | Description |
+| --- | --- | --- |
+| `total` | `number` | The total number of data |
+| `pagination` | `object` | |
+| `pagination.total` | `number` | The number of available data with the current query |
+| `pagination.pages` | `object` | |
+| `pagination.pages.first` | `number` | The first page number |
+| `pagination.pages.last` | `number` | The last page number |
+| `pagination.pages.current` | `number` or `null` | The current page number or `null` if the `page` query is exceeded the last page |
+| `pagination.pages.previous` | `number` or `null` | The previous page number or `null` if the current page is the first page |
+| `pagination.pages.next` | `number` or `null` | The next page number or `null` if the current page is the last page |
diff --git a/src/district/__mocks__/district.service.ts b/src/district/__mocks__/district.service.ts
index 96f79f0..b569673 100644
--- a/src/district/__mocks__/district.service.ts
+++ b/src/district/__mocks__/district.service.ts
@@ -1,15 +1,12 @@
import { sortArray } from '@/common/utils/array';
-import { SortOptions } from '@/sort/sort.service';
-import { District, Village } from '@prisma/client';
+import { District } from '@prisma/client';
import { DistrictFindQueries } from '../district.dto';
export class MockDistrictService {
readonly districts: District[];
- readonly villages: Village[];
- constructor(districts: District[], villages: Village[]) {
+ constructor(districts: District[]) {
this.districts = districts;
- this.villages = villages;
}
async find({
@@ -34,19 +31,4 @@ export class MockDistrictService {
this.districts.find((district) => district.code === code) ?? null,
);
}
-
- async findVillages(
- districtCode: string,
- { sortBy = 'code', sortOrder }: SortOptions = {},
- ) {
- if (this.districts.every((p) => p.code !== districtCode)) {
- return null;
- }
-
- const res = this.villages.filter(
- (village) => village.districtCode === districtCode,
- );
-
- return Promise.resolve({ data: sortArray(res, sortBy, sortOrder) });
- }
}
diff --git a/src/district/district.controller.spec.ts b/src/district/district.controller.spec.ts
index 6277273..9433be1 100644
--- a/src/district/district.controller.spec.ts
+++ b/src/district/district.controller.spec.ts
@@ -1,9 +1,9 @@
import { getValues, sortArray } from '@/common/utils/array';
-import { getDistricts, getVillages } from '@/common/utils/data';
+import { getDistricts } from '@/common/utils/data';
import { SortOrder } from '@/sort/sort.dto';
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
-import { District, Village } from '@prisma/client';
+import { District } from '@prisma/client';
import { MockDistrictService } from './__mocks__/district.service';
import { DistrictController } from './district.controller';
import { DistrictService } from './district.service';
@@ -12,12 +12,10 @@ describe('DistrictController', () => {
const testDistrictCode = '110101';
let districts: District[];
- let villages: Village[];
let controller: DistrictController;
beforeAll(async () => {
districts = await getDistricts();
- villages = await getVillages();
});
beforeEach(async () => {
@@ -26,7 +24,7 @@ describe('DistrictController', () => {
providers: [
{
provide: DistrictService,
- useValue: new MockDistrictService(districts, villages),
+ useValue: new MockDistrictService(districts),
},
],
}).compile();
@@ -149,64 +147,4 @@ describe('DistrictController', () => {
).rejects.toThrowError(NotFoundException);
});
});
-
- describe('findVillages', () => {
- let expectedVillages: Village[];
-
- beforeAll(() => {
- expectedVillages = villages.filter(
- (p) => p.districtCode === testDistrictCode,
- );
- });
-
- it('should return all villages in the matching district', async () => {
- const { data } = await controller.findVillages({
- code: testDistrictCode,
- });
-
- for (const village of data) {
- expect(village).toEqual(
- expect.objectContaining({
- code: expect.stringMatching(
- new RegExp(`^${testDistrictCode}\\d{4}$`),
- ),
- name: expect.any(String),
- districtCode: testDistrictCode,
- }),
- );
- }
-
- expect(data).toHaveLength(
- villages.filter((p) => p.districtCode === testDistrictCode).length,
- );
- });
-
- it('should throw NotFoundException if there is no matching district', async () => {
- await expect(
- controller.findVillages({ code: '000000' }),
- ).rejects.toThrowError(NotFoundException);
- });
-
- it('should return all villages in the matching district sorted by name ascending', async () => {
- const { data } = await controller.findVillages(
- { code: testDistrictCode },
- { sortBy: 'name' },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedVillages, 'name'), 'code'),
- );
- });
-
- it('should return all villages in the matching district sorted by name descending', async () => {
- const { data } = await controller.findVillages(
- { code: testDistrictCode },
- { sortBy: 'name', sortOrder: SortOrder.DESC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedVillages, 'name', SortOrder.DESC), 'code'),
- );
- });
- });
});
diff --git a/src/district/district.controller.ts b/src/district/district.controller.ts
index 4aef19a..da37efd 100644
--- a/src/district/district.controller.ts
+++ b/src/district/district.controller.ts
@@ -1,5 +1,4 @@
import { ApiDataResponse } from '@/common/decorator/api-data-response.decorator';
-import { Village } from '@/village/village.dto';
import {
Controller,
Get,
@@ -18,8 +17,6 @@ import {
District,
DistrictFindByCodeParams,
DistrictFindQueries,
- DistrictFindVillageParams,
- DistrictFindVillageQueries,
} from './district.dto';
import { DistrictService } from './district.service';
import { ApiPaginatedResponse } from '@/common/decorator/api-paginated-response.decorator';
@@ -30,7 +27,7 @@ import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
export class DistrictController {
constructor(private readonly districtService: DistrictService) {}
- @ApiOperation({ description: 'Get districts by its name.' })
+ @ApiOperation({ description: 'Get the districts.' })
@ApiQuery({
name: 'sortBy',
description: 'Sort by district code or name.',
@@ -68,32 +65,4 @@ export class DistrictController {
return district;
}
-
- @ApiOperation({ description: 'Get all villages in a district.' })
- @ApiQuery({
- name: 'sortBy',
- description: 'Sort villages by its code or name.',
- required: false,
- type: 'string',
- example: 'code',
- })
- @ApiPaginatedResponse({
- model: Village,
- description: 'Returns array of villages.',
- })
- @ApiBadRequestResponse({ description: 'If the `code` is invalid.' })
- @ApiNotFoundResponse({
- description: 'If there are no district match with the `code`.',
- })
- @Get(':code/villages')
- async findVillages(
- @Param() { code }: DistrictFindVillageParams,
- @Query() queries?: DistrictFindVillageQueries,
- ): Promise> {
- if ((await this.districtService.findByCode(code)) === null) {
- throw new NotFoundException(`There are no district with code '${code}'`);
- }
-
- return this.districtService.findVillages(code, queries);
- }
}
diff --git a/src/district/district.dto.ts b/src/district/district.dto.ts
index 1eb31e4..ce01d42 100644
--- a/src/district/district.dto.ts
+++ b/src/district/district.dto.ts
@@ -8,7 +8,6 @@ import {
PickType,
} from '@nestjs/swagger';
import { IsNotEmpty, IsNumberString, Length } from 'class-validator';
-import { VillageSortQuery } from '../village/village.dto';
import { PaginationQuery } from '@/common/dto/pagination.dto';
export class District {
@@ -48,12 +47,3 @@ export class DistrictFindQueries extends IntersectionType(
export class DistrictFindByCodeParams extends PickType(District, [
'code',
] as const) {}
-
-export class DistrictFindVillageParams extends PickType(District, [
- 'code',
-] as const) {}
-
-export class DistrictFindVillageQueries extends IntersectionType(
- VillageSortQuery,
- PaginationQuery,
-) {}
diff --git a/src/district/district.service.spec.ts b/src/district/district.service.spec.ts
index e4b7709..fce24a2 100644
--- a/src/district/district.service.spec.ts
+++ b/src/district/district.service.spec.ts
@@ -1,31 +1,23 @@
+import { getDistricts } from '@/common/utils/data';
+import { getDBProviderFeatures } from '@/common/utils/db';
+import { SortOrder } from '@/sort/sort.dto';
import { Test, TestingModule } from '@nestjs/testing';
-import { District, Village } from '@prisma/client';
+import { District } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { DistrictService } from './district.service';
-import { VillageService } from '@/village/village.service';
-import { getDBProviderFeatures } from '@/common/utils/db';
-import { SortOrder } from '@/sort/sort.dto';
-
-const districts: readonly District[] = [
- { code: '110101', name: 'Bakongan', regencyCode: '1101' },
- { code: '110102', name: 'Kluet Utara', regencyCode: '1101' },
- { code: '110103', name: 'Kluet Selatan', regencyCode: '1101' },
-];
-
-const villages: readonly Village[] = [
- { code: '1101012001', name: 'Desa 1', districtCode: '110101' },
- { code: '1101012002', name: 'Desa 2', districtCode: '110101' },
- { code: '1212121001', name: 'Kampung Karet', districtCode: '121212' },
- { code: '1212121002', name: 'Kampung Berkah', districtCode: '121212' },
-] as const;
describe('DistrictService', () => {
+ let districts: District[];
let service: DistrictService;
let prismaService: PrismaService;
+ beforeAll(async () => {
+ districts = await getDistricts();
+ });
+
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
- providers: [DistrictService, PrismaService, VillageService],
+ providers: [DistrictService, PrismaService],
}).compile();
service = module.get(DistrictService);
@@ -187,43 +179,4 @@ describe('DistrictService', () => {
expect(result).toEqual(expectedDistrict);
});
});
-
- describe('findVillages', async () => {
- const getPaginatorOptions = (testCode: string) => ({
- model: 'Village',
- paginate: { page: undefined, limit: undefined },
- args: { where: { districtCode: testCode } },
- });
-
- it('should return empty array if there is no match district code', async () => {
- const testCode = '999999';
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: [] });
-
- const result = await service.findVillages(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
- expect(result.data).toEqual([]);
- });
-
- it('should return all villages in a district', async () => {
- const testCode = '110101';
- const expectedVillages = villages.filter(
- (v) => v.districtCode === testCode,
- );
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: expectedVillages });
-
- const result = await service.findVillages(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
- expect(result.data).toEqual(expectedVillages);
- });
- });
});
diff --git a/src/district/district.service.ts b/src/district/district.service.ts
index 7db47c1..362feab 100644
--- a/src/district/district.service.ts
+++ b/src/district/district.service.ts
@@ -1,21 +1,16 @@
-import { PaginationQuery } from '@/common/dto/pagination.dto';
import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
import { getDBProviderFeatures } from '@/common/utils/db';
import { PrismaService } from '@/prisma/prisma.service';
-import { SortOptions, SortService } from '@/sort/sort.service';
-import { VillageService } from '@/village/village.service';
+import { SortService } from '@/sort/sort.service';
import { Injectable } from '@nestjs/common';
-import { District, Village } from '@prisma/client';
+import { District } from '@prisma/client';
import { DistrictFindQueries } from './district.dto';
@Injectable()
export class DistrictService {
readonly sorter: SortService;
- constructor(
- private readonly prisma: PrismaService,
- private readonly villageService: VillageService,
- ) {
+ constructor(private readonly prisma: PrismaService) {
this.sorter = new SortService({
sortBy: 'code',
sortOrder: 'asc',
@@ -54,28 +49,4 @@ export class DistrictService {
where: { code },
});
}
-
- /**
- * Find all villages in a district.
- * @param districtCode The district code.
- * @param options The options.
- * @returns Paginated array of villages, `[]` if there are no match district.
- */
- async findVillages(
- districtCode: string,
- options?: SortOptions & PaginationQuery,
- ): Promise> {
- const { page, limit, sortBy, sortOrder } = options ?? {};
-
- return this.prisma.paginator({
- model: 'Village',
- paginate: { page, limit },
- args: {
- where: { districtCode },
- ...((sortBy || sortOrder) && {
- orderBy: this.villageService.sorter.object({ sortBy, sortOrder }),
- }),
- },
- });
- }
}
diff --git a/src/island/island.controller.ts b/src/island/island.controller.ts
index 50af02a..7b9aff1 100644
--- a/src/island/island.controller.ts
+++ b/src/island/island.controller.ts
@@ -28,7 +28,7 @@ export class IslandController {
constructor(private readonly islandService: IslandService) {}
@ApiOperation({
- description: 'Get the islands by its name.',
+ description: 'Get the islands.',
})
@ApiQuery({
name: 'sortBy',
diff --git a/src/province/__mocks__/province.service.ts b/src/province/__mocks__/province.service.ts
index a403744..783aa9d 100644
--- a/src/province/__mocks__/province.service.ts
+++ b/src/province/__mocks__/province.service.ts
@@ -1,15 +1,12 @@
import { sortArray } from '@/common/utils/array';
-import { SortOptions } from '@/sort/sort.service';
-import { Province, Regency } from '@prisma/client';
+import { Province } from '@prisma/client';
import { ProvinceFindQueries } from '../province.dto';
export class MockProvinceService {
readonly provinces: readonly Province[];
- readonly regencies: readonly Regency[];
- constructor(provinces: Province[], regencies: Regency[]) {
+ constructor(provinces: Province[]) {
this.provinces = provinces;
- this.regencies = regencies;
}
async find({
@@ -29,15 +26,4 @@ export class MockProvinceService {
this.provinces.find((province) => province.code === code) ?? null,
);
}
-
- async findRegencies(
- provinceCode: string,
- { sortBy = 'code', sortOrder }: SortOptions = {},
- ) {
- const res = this.regencies.filter(
- (regency) => regency.provinceCode === provinceCode,
- );
-
- return Promise.resolve({ data: sortArray(res, sortBy, sortOrder) });
- }
}
diff --git a/src/province/province.controller.spec.ts b/src/province/province.controller.spec.ts
index b4ee5a8..5c65615 100644
--- a/src/province/province.controller.spec.ts
+++ b/src/province/province.controller.spec.ts
@@ -1,9 +1,9 @@
import { getValues, sortArray } from '@/common/utils/array';
-import { getProvinces, getRegencies } from '@/common/utils/data';
+import { getProvinces } from '@/common/utils/data';
import { SortOrder } from '@/sort/sort.dto';
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
-import { Province, Regency } from '@prisma/client';
+import { Province } from '@prisma/client';
import { MockProvinceService } from './__mocks__/province.service';
import { ProvinceController } from './province.controller';
import { ProvinceService } from './province.service';
@@ -13,11 +13,9 @@ describe('ProvinceController', () => {
let controller: ProvinceController;
let provinces: Province[];
- let regencies: Regency[];
beforeAll(async () => {
provinces = await getProvinces();
- regencies = await getRegencies();
});
beforeEach(async () => {
@@ -26,7 +24,7 @@ describe('ProvinceController', () => {
providers: [
{
provide: ProvinceService,
- useValue: new MockProvinceService(provinces, regencies),
+ useValue: new MockProvinceService(provinces),
},
],
}).compile();
@@ -113,61 +111,4 @@ describe('ProvinceController', () => {
);
});
});
-
- describe('findRegencies', () => {
- let expectedRegencies: Regency[];
-
- beforeAll(() => {
- expectedRegencies = regencies.filter(
- (r) => r.provinceCode === testProvCode,
- );
- });
-
- it('should return all regencies in the matching province', async () => {
- const { data } = await controller.findRegencies({
- code: testProvCode,
- });
-
- for (const regency of data) {
- expect(regency).toEqual(
- expect.objectContaining({
- code: expect.stringMatching(new RegExp(`^${testProvCode}\\d{2}$`)),
- name: expect.any(String),
- provinceCode: testProvCode,
- }),
- );
- }
-
- expect(data).toEqual(expect.arrayContaining([]));
- expect(data).toHaveLength(expectedRegencies.length);
- });
-
- it('should throw NotFoundException if there is no matching province', async () => {
- await expect(
- controller.findRegencies({ code: '00' }),
- ).rejects.toThrowError(NotFoundException);
- });
-
- it('should return all regencies in the matching province sorted by name ascending', async () => {
- const { data } = await controller.findRegencies(
- { code: testProvCode },
- { sortBy: 'name', sortOrder: SortOrder.ASC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedRegencies, 'name'), 'code'),
- );
- });
-
- it('should return all regencies in the matching province sorted by name descending', async () => {
- const { data } = await controller.findRegencies(
- { code: testProvCode },
- { sortBy: 'name', sortOrder: SortOrder.DESC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedRegencies, 'name', SortOrder.DESC), 'code'),
- );
- });
- });
});
diff --git a/src/province/province.controller.ts b/src/province/province.controller.ts
index 70217d6..566ab9a 100644
--- a/src/province/province.controller.ts
+++ b/src/province/province.controller.ts
@@ -1,5 +1,4 @@
import { ApiDataResponse } from '@/common/decorator/api-data-response.decorator';
-import { Regency } from '@/regency/regency.dto';
import {
Controller,
Get,
@@ -18,8 +17,6 @@ import {
Province,
ProvinceFindByCodeParams,
ProvinceFindQueries,
- ProvinceFindRegencyParams,
- ProvinceFindRegencyQueries,
} from './province.dto';
import { ProvinceService } from './province.service';
import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
@@ -69,32 +66,4 @@ export class ProvinceController {
return province;
}
-
- @ApiOperation({ description: 'Get all regencies in a province.' })
- @ApiQuery({
- name: 'sortBy',
- description: 'Sort regencies by its code or name.',
- required: false,
- type: 'string',
- example: 'code',
- })
- @ApiPaginatedResponse({
- model: Regency,
- description: 'Returns array of regencies.',
- })
- @ApiBadRequestResponse({ description: 'If the `code` is invalid.' })
- @ApiNotFoundResponse({
- description: 'If there are no province match with the `code`.',
- })
- @Get(':code/regencies')
- async findRegencies(
- @Param() { code }: ProvinceFindRegencyParams,
- @Query() queries?: ProvinceFindRegencyQueries,
- ): Promise> {
- if ((await this.provinceService.findByCode(code)) === null) {
- throw new NotFoundException(`There are no province with code '${code}'`);
- }
-
- return this.provinceService.findRegencies(code, queries);
- }
}
diff --git a/src/province/province.dto.ts b/src/province/province.dto.ts
index ed1510b..8eac025 100644
--- a/src/province/province.dto.ts
+++ b/src/province/province.dto.ts
@@ -8,7 +8,6 @@ import {
PickType,
} from '@nestjs/swagger';
import { IsNotEmpty, IsNumberString, Length } from 'class-validator';
-import { RegencySortQuery } from '../regency/regency.dto';
import { PaginationQuery } from '@/common/dto/pagination.dto';
export class Province {
@@ -39,12 +38,3 @@ export class ProvinceFindQueries extends IntersectionType(
export class ProvinceFindByCodeParams extends PickType(Province, [
'code',
] as const) {}
-
-export class ProvinceFindRegencyParams extends PickType(Province, [
- 'code',
-] as const) {}
-
-export class ProvinceFindRegencyQueries extends IntersectionType(
- RegencySortQuery,
- PaginationQuery,
-) {}
diff --git a/src/province/province.service.spec.ts b/src/province/province.service.spec.ts
index ee3b083..c747117 100644
--- a/src/province/province.service.spec.ts
+++ b/src/province/province.service.spec.ts
@@ -1,45 +1,23 @@
+import { getProvinces } from '@/common/utils/data';
import { getDBProviderFeatures } from '@/common/utils/db';
-import { DistrictService } from '@/district/district.service';
-import { IslandService } from '@/island/island.service';
import { PrismaService } from '@/prisma/prisma.service';
-import { RegencyService } from '@/regency/regency.service';
import { SortOrder } from '@/sort/sort.dto';
-import { VillageService } from '@/village/village.service';
import { Test, TestingModule } from '@nestjs/testing';
-import { Province, Regency } from '@prisma/client';
+import { Province } from '@prisma/client';
import { ProvinceService } from './province.service';
-const provinces: readonly Province[] = [
- { code: '11', name: 'ACEH' },
- { code: '12', name: 'SUMATERA UTARA' },
- { code: '32', name: 'JAWA BARAT' },
- { code: '33', name: 'JAWA TENGAH' },
- { code: '34', name: 'DI YOGYAKARTA' },
- { code: '35', name: 'JAWA TIMUR' },
-] as const;
-
-const regencies: readonly Regency[] = [
- { code: '1101', name: 'KABUPATEN ACEH SELATAN', provinceCode: '11' },
- { code: '1102', name: 'KABUPATEN ACEH TENGGARA', provinceCode: '11' },
- { code: '1201', name: 'KABUPATEN TAPANULI TENGAH', provinceCode: '12' },
- { code: '1202', name: 'KABUPATEN TAPANULI UTARA', provinceCode: '12' },
- { code: '1271', name: 'KOTA MEDAN', provinceCode: '12' },
-] as const;
-
describe('ProvinceService', () => {
+ let provinces: readonly Province[];
let provinceService: ProvinceService;
let prismaService: PrismaService;
+ beforeAll(async () => {
+ provinces = await getProvinces();
+ });
+
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
- providers: [
- ProvinceService,
- PrismaService,
- RegencyService,
- DistrictService,
- IslandService,
- VillageService,
- ],
+ providers: [ProvinceService, PrismaService],
}).compile();
provinceService = module.get(ProvinceService);
@@ -176,58 +154,4 @@ describe('ProvinceService', () => {
expect(result).toBeNull();
});
});
-
- describe('findRegencies', () => {
- const getPaginatorOptions = (testCode: string) => ({
- model: 'Regency',
- paginate: { limit: undefined, page: undefined },
- args: { where: { provinceCode: testCode }, orderBy: { code: 'asc' } },
- });
-
- it('should return all regencies in a province', async () => {
- const testCode = '11';
- const expectedRegencies = regencies.filter(
- (r) => r.provinceCode === testCode,
- );
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: expectedRegencies });
-
- const result = await provinceService.findRegencies(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
-
- expect(result.data).toEqual(expectedRegencies);
- });
-
- it('should return empty array if there is no match province code', async () => {
- const testCode = '9999';
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: [] });
-
- const result = await provinceService.findRegencies(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
- expect(result.data).toEqual([]);
- });
-
- it.todo(
- 'should return regencies sorted by name in ascending order',
- async () => {
- // Test implementation goes here
- },
- );
-
- it.todo(
- 'should return regencies sorted by name in descending order',
- async () => {
- // Test implementation goes here
- },
- );
- });
});
diff --git a/src/province/province.service.ts b/src/province/province.service.ts
index 68ff26b..13ff114 100644
--- a/src/province/province.service.ts
+++ b/src/province/province.service.ts
@@ -1,21 +1,16 @@
-import { PaginationQuery } from '@/common/dto/pagination.dto';
import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
import { getDBProviderFeatures } from '@/common/utils/db';
import { PrismaService } from '@/prisma/prisma.service';
-import { RegencyService } from '@/regency/regency.service';
-import { SortOptions, SortService } from '@/sort/sort.service';
+import { SortService } from '@/sort/sort.service';
import { Injectable } from '@nestjs/common';
-import { Province, Regency } from '@prisma/client';
+import { Province } from '@prisma/client';
import { ProvinceFindQueries } from './province.dto';
@Injectable()
export class ProvinceService {
readonly sorter: SortService;
- constructor(
- private readonly prisma: PrismaService,
- private readonly regencyService: RegencyService,
- ) {
+ constructor(private readonly prisma: PrismaService) {
this.sorter = new SortService({
sortBy: 'code',
sortOrder: 'asc',
@@ -56,26 +51,4 @@ export class ProvinceService {
},
});
}
-
- /**
- * Find all regencies in a province.
- * @param provinceCode The province code.
- * @param options The options.
- * @returns An array of regencies, `[]` if there are no match province.
- */
- async findRegencies(
- provinceCode: string,
- options?: SortOptions & PaginationQuery,
- ): Promise> {
- const { sortBy, sortOrder, page, limit } = options ?? {};
-
- return this.prisma.paginator({
- model: 'Regency',
- args: {
- where: { provinceCode },
- orderBy: this.regencyService.sorter.object({ sortBy, sortOrder }),
- },
- paginate: { page, limit },
- });
- }
}
diff --git a/src/regency/__mocks__/regency.service.ts b/src/regency/__mocks__/regency.service.ts
index d5979a0..dab8a7f 100644
--- a/src/regency/__mocks__/regency.service.ts
+++ b/src/regency/__mocks__/regency.service.ts
@@ -1,18 +1,12 @@
import { sortArray } from '@/common/utils/array';
-import { convertCoordinate } from '@/common/utils/coordinate';
-import { SortOptions } from '@/sort/sort.service';
-import { District, Island, Regency } from '@prisma/client';
+import { Regency } from '@prisma/client';
import { RegencyFindQueries } from '../regency.dto';
export class MockRegencyService {
readonly regencies: readonly Regency[];
- readonly districts: readonly District[];
- readonly islands: readonly Island[];
- constructor(regencies: Regency[], districts: District[], islands: Island[]) {
+ constructor(regencies: Regency[]) {
this.regencies = regencies;
- this.districts = districts;
- this.islands = islands;
}
async find({
@@ -37,37 +31,4 @@ export class MockRegencyService {
this.regencies.find((regency) => regency.code === code) ?? null,
);
}
-
- async findDistricts(
- regencyCode: string,
- { sortBy = 'code', sortOrder }: SortOptions = {},
- ) {
- if (this.regencies.every((r) => r.code !== regencyCode)) {
- return null;
- }
-
- const res = this.districts.filter(
- (district) => district.regencyCode === regencyCode,
- );
-
- return Promise.resolve({ data: sortArray(res, sortBy, sortOrder) });
- }
-
- async findIslands(
- regencyCode: string,
- { sortBy = 'code', sortOrder }: SortOptions = {},
- ) {
- if (this.regencies.every((r) => r.code !== regencyCode)) {
- return null;
- }
-
- const res = this.islands
- .filter((island) => island.regencyCode === regencyCode)
- .map((island) => {
- const [latitude, longitude] = convertCoordinate(island.coordinate);
- return { ...island, latitude, longitude };
- });
-
- return Promise.resolve({ data: sortArray(res, sortBy, sortOrder) });
- }
}
diff --git a/src/regency/regency.controller.spec.ts b/src/regency/regency.controller.spec.ts
index c25b9e3..7fea3c6 100644
--- a/src/regency/regency.controller.spec.ts
+++ b/src/regency/regency.controller.spec.ts
@@ -1,9 +1,9 @@
import { getValues, sortArray } from '@/common/utils/array';
-import { getDistricts, getIslands, getRegencies } from '@/common/utils/data';
+import { getRegencies } from '@/common/utils/data';
import { SortOrder } from '@/sort/sort.dto';
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
-import { District, Island, Regency } from '@prisma/client';
+import { Regency } from '@prisma/client';
import { MockRegencyService } from './__mocks__/regency.service';
import { RegencyController } from './regency.controller';
import { RegencyService } from './regency.service';
@@ -12,14 +12,10 @@ describe('RegencyController', () => {
const testRegencyCode = '1101';
let regencies: Regency[];
- let districts: District[];
- let islands: Island[];
let controller: RegencyController;
beforeAll(async () => {
regencies = await getRegencies();
- districts = await getDistricts();
- islands = await getIslands();
});
beforeEach(async () => {
@@ -28,7 +24,7 @@ describe('RegencyController', () => {
providers: [
{
provide: RegencyService,
- useValue: new MockRegencyService(regencies, districts, islands),
+ useValue: new MockRegencyService(regencies),
},
],
}).compile();
@@ -147,150 +143,4 @@ describe('RegencyController', () => {
).rejects.toThrowError(NotFoundException);
});
});
-
- describe('findDistricts', () => {
- let expectedDistricts: District[];
-
- beforeAll(() => {
- expectedDistricts = districts.filter(
- (r) => r.regencyCode === testRegencyCode,
- );
- });
-
- it('should return all districts in the matching regency', async () => {
- const { data } = await controller.findDistricts({
- code: testRegencyCode,
- });
-
- for (const district of data) {
- expect(district).toEqual(
- expect.objectContaining({
- code: expect.stringMatching(
- new RegExp(`^${testRegencyCode}\\d{2}$`),
- ),
- name: expect.any(String),
- regencyCode: testRegencyCode,
- }),
- );
- }
-
- expect(data).toHaveLength(expectedDistricts.length);
- });
-
- it('should throw NotFoundException if there is no matching regency', async () => {
- await expect(
- controller.findDistricts({ code: '0000' }),
- ).rejects.toThrowError(NotFoundException);
- });
-
- it('should return all districts in the matching regency sorted by name ascending', async () => {
- const { data } = await controller.findDistricts(
- { code: testRegencyCode },
- { sortBy: 'name', sortOrder: SortOrder.ASC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedDistricts, 'name'), 'code'),
- );
- });
-
- it('should return all districts in the matching regency sorted by name descending', async () => {
- const { data } = await controller.findDistricts(
- { code: testRegencyCode },
- { sortBy: 'name', sortOrder: SortOrder.DESC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedDistricts, 'name', SortOrder.DESC), 'code'),
- );
- });
- });
-
- describe('findIslands', () => {
- let expectedIslands: Island[];
-
- beforeAll(() => {
- expectedIslands = islands.filter(
- (r) => r.regencyCode === testRegencyCode,
- );
- });
-
- it('should return all islands in the matching regency', async () => {
- const { data } = await controller.findIslands({
- code: testRegencyCode,
- });
-
- for (const island of data) {
- expect(island).toEqual(
- expect.objectContaining({
- code: expect.stringMatching(
- new RegExp(`^${testRegencyCode}\\d{5}$`),
- ),
- coordinate: expect.any(String),
- isOutermostSmall: expect.any(Boolean),
- isPopulated: expect.any(Boolean),
- latitude: expect.any(Number),
- longitude: expect.any(Number),
- name: expect.any(String),
- regencyCode: testRegencyCode,
- }),
- );
- }
-
- expect(data).toHaveLength(expectedIslands.length);
- });
-
- it('should throw NotFoundException if there is no matching regency', async () => {
- await expect(
- controller.findIslands({ code: '0000' }),
- ).rejects.toThrowError(NotFoundException);
- });
-
- it('should return all islands in the matching regency sorted by name ascending', async () => {
- const { data } = await controller.findIslands(
- { code: testRegencyCode },
- { sortBy: 'name', sortOrder: SortOrder.ASC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedIslands, 'name'), 'code'),
- );
- });
-
- it('should return all islands in the matching regency sorted by name descending', async () => {
- const { data } = await controller.findIslands(
- { code: testRegencyCode },
- { sortBy: 'name', sortOrder: SortOrder.DESC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedIslands, 'name', SortOrder.DESC), 'code'),
- );
- });
-
- it('should return all islands in the matching regency sorted by coordinate ascending', async () => {
- const { data } = await controller.findIslands(
- { code: testRegencyCode },
- { sortBy: 'coordinate', sortOrder: SortOrder.ASC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(sortArray(expectedIslands, 'coordinate'), 'code'),
- );
- });
-
- it('should return all islands in the matching regency sorted by coordinate descending', async () => {
- const { data } = await controller.findIslands(
- { code: testRegencyCode },
- { sortBy: 'coordinate', sortOrder: SortOrder.DESC },
- );
-
- expect(getValues(data, 'code')).toEqual(
- getValues(
- sortArray(expectedIslands, 'coordinate', SortOrder.DESC),
- 'code',
- ),
- );
- });
- });
});
diff --git a/src/regency/regency.controller.ts b/src/regency/regency.controller.ts
index 230b4db..ab4b95b 100644
--- a/src/regency/regency.controller.ts
+++ b/src/regency/regency.controller.ts
@@ -1,7 +1,5 @@
import { ApiDataResponse } from '@/common/decorator/api-data-response.decorator';
import { ApiPaginatedResponse } from '@/common/decorator/api-paginated-response.decorator';
-import { District } from '@/district/district.dto';
-import { Island } from '@/island/island.dto';
import {
Controller,
Get,
@@ -19,9 +17,6 @@ import {
import {
Regency,
RegencyFindByCodeParams,
- RegencyFindDistrictParams,
- RegencyFindDistrictQueries,
- RegencyFindIslandsQueries,
RegencyFindQueries,
} from './regency.dto';
import { RegencyService } from './regency.service';
@@ -70,60 +65,4 @@ export class RegencyController {
return regency;
}
-
- @ApiOperation({ description: 'Get all districts in a regency.' })
- @ApiQuery({
- name: 'sortBy',
- description: 'Sort districts by its code or name.',
- required: false,
- type: 'string',
- example: 'code',
- })
- @ApiPaginatedResponse({
- model: District,
- description: 'Returns array of districts.',
- })
- @ApiBadRequestResponse({ description: 'If the `code` is invalid.' })
- @ApiNotFoundResponse({
- description: 'If there are no regency match with the `code`.',
- })
- @Get(':code/districts')
- async findDistricts(
- @Param() { code }: RegencyFindDistrictParams,
- @Query() queries?: RegencyFindDistrictQueries,
- ): Promise> {
- if ((await this.regencyService.findByCode(code)) === null) {
- throw new NotFoundException(`There are no regency with code '${code}'`);
- }
-
- return this.regencyService.findDistricts(code, queries);
- }
-
- @ApiOperation({ description: 'Get all islands in a regency.' })
- @ApiQuery({
- name: 'sortBy',
- description: 'Sort islands by its code, name, or coordinate.',
- required: false,
- type: 'string',
- example: 'code',
- })
- @ApiPaginatedResponse({
- model: Island,
- description: 'Returns array of islands.',
- })
- @ApiBadRequestResponse({ description: 'If the `code` is invalid.' })
- @ApiNotFoundResponse({
- description: 'If there are no regency match with the `code`.',
- })
- @Get(':code/islands')
- async findIslands(
- @Param() { code }: RegencyFindByCodeParams,
- @Query() queries?: RegencyFindIslandsQueries,
- ) {
- if ((await this.regencyService.findByCode(code)) === null) {
- throw new NotFoundException(`There are no regency with code '${code}'`);
- }
-
- return this.regencyService.findIslands(code, queries);
- }
}
diff --git a/src/regency/regency.dto.ts b/src/regency/regency.dto.ts
index db668c4..e25d1de 100644
--- a/src/regency/regency.dto.ts
+++ b/src/regency/regency.dto.ts
@@ -8,8 +8,6 @@ import {
PickType,
} from '@nestjs/swagger';
import { IsNotEmpty, IsNumberString, Length } from 'class-validator';
-import { DistrictSortQuery } from '../district/district.dto';
-import { IslandSortQuery } from '../island/island.dto';
import { PaginationQuery } from '@/common/dto/pagination.dto';
export class Regency {
@@ -52,17 +50,3 @@ export class RegencyFindQueries extends IntersectionType(
export class RegencyFindByCodeParams extends PickType(Regency, [
'code',
] as const) {}
-
-export class RegencyFindDistrictParams extends PickType(Regency, [
- 'code',
-] as const) {}
-
-export class RegencyFindDistrictQueries extends IntersectionType(
- DistrictSortQuery,
- PaginationQuery,
-) {}
-
-export class RegencyFindIslandsQueries extends IntersectionType(
- IslandSortQuery,
- PaginationQuery,
-) {}
diff --git a/src/regency/regency.service.spec.ts b/src/regency/regency.service.spec.ts
index 06679cd..c5ff936 100644
--- a/src/regency/regency.service.spec.ts
+++ b/src/regency/regency.service.spec.ts
@@ -1,83 +1,23 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PrismaService } from '@/prisma/prisma.service';
import { RegencyService } from './regency.service';
-import { DistrictService } from '@/district/district.service';
-import { IslandService } from '@/island/island.service';
-import { District, Island, Regency } from '@prisma/client';
-import { VillageService } from '@/village/village.service';
+import { Regency } from '@prisma/client';
import { getDBProviderFeatures } from '@/common/utils/db';
import { SortOrder } from '@/sort/sort.dto';
-
-const regencies: readonly Regency[] = [
- { code: '1101', name: 'KABUPATEN ACEH SELATAN', provinceCode: '11' },
- { code: '1102', name: 'KABUPATEN ACEH TENGGARA', provinceCode: '11' },
- { code: '1201', name: 'KABUPATEN TAPANULI TENGAH', provinceCode: '12' },
- { code: '1202', name: 'KABUPATEN TAPANULI UTARA', provinceCode: '12' },
- { code: '1271', name: 'KOTA MEDAN', provinceCode: '12' },
-] as const;
-
-const districts: readonly District[] = [
- { code: '110101', name: 'Bakongan', regencyCode: '1101' },
- { code: '110102', name: 'Kluet Utara', regencyCode: '1101' },
- { code: '110103', name: 'Kluet Selatan', regencyCode: '1101' },
-];
-
-const islands: readonly Island[] = [
- {
- code: '110140001',
- coordinate: '03°19\'03.44" N 097°07\'41.73" E',
- isOutermostSmall: false,
- isPopulated: false,
- name: 'Pulau Batukapal',
- regencyCode: '1101',
- },
- {
- code: '110140002',
- coordinate: '03°24\'55.00" N 097°04\'21.00" E',
- isOutermostSmall: false,
- isPopulated: false,
- name: 'Pulau Batutunggal',
- regencyCode: '1101',
- },
- {
- code: '110140003',
- coordinate: '02°52\'54.99" N 097°31\'07.00" E',
- isOutermostSmall: false,
- isPopulated: false,
- name: 'Pulau Kayee',
- regencyCode: '1101',
- },
- {
- code: '110140004',
- coordinate: '02°54\'25.11" N 097°26\'18.51" E',
- isOutermostSmall: false,
- isPopulated: true,
- name: 'Pulau Mangki Palsu',
- regencyCode: '1101',
- },
- {
- code: '110140005',
- coordinate: '02°53\'16.00" N 097°30\'54.00" E',
- isOutermostSmall: true,
- isPopulated: false,
- name: 'Pulau Tengku Palsu',
- regencyCode: '1101',
- },
-] as const;
+import { getRegencies } from '@/common/utils/data';
describe('RegencyService', () => {
+ let regencies: Regency[];
let service: RegencyService;
let prismaService: PrismaService;
+ beforeAll(async () => {
+ regencies = await getRegencies();
+ });
+
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
- providers: [
- RegencyService,
- PrismaService,
- DistrictService,
- IslandService,
- VillageService,
- ],
+ providers: [RegencyService, PrismaService],
}).compile();
service = module.get(RegencyService);
@@ -233,122 +173,4 @@ describe('RegencyService', () => {
expect(result).toBeNull();
});
});
-
- describe('findDistricts', () => {
- const getPaginatorOptions = (testCode: string) => ({
- model: 'District',
- paginate: { page: undefined, limit: undefined },
- args: { where: { regencyCode: testCode }, orderBy: { code: 'asc' } },
- });
-
- it('should return all districts in a regency', async () => {
- const testCode = '1101';
- const expectedDistricts = districts.filter(
- (d) => d.regencyCode === testCode,
- );
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: [...expectedDistricts] });
-
- const result = await service.findDistricts(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
- expect(result.data).toEqual(expectedDistricts);
- });
-
- it('should return empty array if there is no match regency code', async () => {
- const testCode = '9999';
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: [] });
-
- const result = await service.findDistricts(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
- expect(result.data).toEqual([]);
- });
-
- it.todo('should sort districts by name in ascending order', async () => {
- const testCode = '1101';
- const expectedDistricts = [...districts].sort((a, b) =>
- a.name.localeCompare(b.name),
- );
-
- const findUniqueSpy = vitest
- .spyOn(prismaService.regency, 'findUnique')
- .mockReturnValue({
- districts: vitest.fn().mockResolvedValue(expectedDistricts),
- } as any);
-
- const result = await service.findDistricts(testCode, {
- sortBy: 'name',
- });
-
- expect(findUniqueSpy).toHaveBeenCalledTimes(1);
- expect(findUniqueSpy).toHaveBeenCalledWith({
- where: { code: testCode },
- });
- // TODO: test if findUnique().districts() is called with the sort options
- expect(result).toEqual(expectedDistricts);
- });
-
- it.todo('should sort districts by name in descending order', async () => {
- //
- });
- });
-
- describe('findIslands', () => {
- const getPaginatorOptions = (testCode: string) => ({
- model: 'Island',
- paginate: { page: undefined, limit: undefined },
- args: { where: { regencyCode: testCode }, orderBy: { code: 'asc' } },
- });
-
- it('should return all islands in a regency', async () => {
- const testCode = '1101';
- const expectedIslands = islands.filter((i) => i.regencyCode === testCode);
-
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: expectedIslands });
-
- const result = await service.findIslands(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
-
- for (const island of result.data) {
- expect(island).toEqual({
- ...island,
- latitude: expect.any(Number),
- longitude: expect.any(Number),
- });
- }
- });
-
- it('should return empty array if there is no match regency code', async () => {
- const testCode = '9999';
- const paginatorSpy = vitest
- .spyOn(prismaService, 'paginator')
- .mockResolvedValue({ data: [] });
-
- const result = await service.findIslands(testCode);
-
- expect(paginatorSpy).toHaveBeenCalledTimes(1);
- expect(paginatorSpy).toHaveBeenCalledWith(getPaginatorOptions(testCode));
- expect(result.data).toEqual([]);
- });
-
- it.todo('should sort islands by code in ascending order ', async () => {
- //
- });
-
- it.todo('should sort islands by code in descending order', async () => {
- //
- });
- });
});
diff --git a/src/regency/regency.service.ts b/src/regency/regency.service.ts
index 098682f..8a2c175 100644
--- a/src/regency/regency.service.ts
+++ b/src/regency/regency.service.ts
@@ -1,24 +1,16 @@
-import { PaginationQuery } from '@/common/dto/pagination.dto';
import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
import { getDBProviderFeatures } from '@/common/utils/db';
-import { DistrictService } from '@/district/district.service';
-import { Island as IslandDTO } from '@/island/island.dto';
-import { IslandService } from '@/island/island.service';
import { PrismaService } from '@/prisma/prisma.service';
-import { SortOptions, SortService } from '@/sort/sort.service';
+import { SortService } from '@/sort/sort.service';
import { Injectable } from '@nestjs/common';
-import { District, Island, Regency } from '@prisma/client';
+import { Regency } from '@prisma/client';
import { RegencyFindQueries } from './regency.dto';
@Injectable()
export class RegencyService {
readonly sorter: SortService;
- constructor(
- private readonly prisma: PrismaService,
- private readonly districtService: DistrictService,
- private readonly islandService: IslandService,
- ) {
+ constructor(private readonly prisma: PrismaService) {
this.sorter = new SortService({
sortBy: 'code',
sortOrder: 'asc',
@@ -58,51 +50,4 @@ export class RegencyService {
},
});
}
-
- /**
- * Find all districts in a regency.
- * @param regencyCode The regency code.
- * @param options The options.
- * @returns Paginated array of districts, `[]` if there are no match regency.
- */
- async findDistricts(
- regencyCode: string,
- options?: SortOptions & PaginationQuery,
- ): Promise> {
- const { page, limit, sortBy, sortOrder } = options ?? {};
-
- return this.prisma.paginator({
- model: 'District',
- args: {
- where: { regencyCode },
- orderBy: this.districtService.sorter.object({ sortBy, sortOrder }),
- },
- paginate: { page, limit },
- });
- }
-
- /**
- * Find all islands in a regency.
- * @param regencyCode The regency code.
- * @param options The options.
- * @returns Paginated array of islands, `[]` if there are no match regency.
- */
- async findIslands(
- regencyCode: string,
- options?: SortOptions & PaginationQuery,
- ): Promise> {
- const { page, limit, sortBy, sortOrder } = options ?? {};
-
- const res = await this.prisma.paginator({
- model: 'Island',
- paginate: { page, limit },
- args: {
- where: { regencyCode },
- orderBy: this.islandService.sorter.object({ sortBy, sortOrder }),
- },
- });
-
- const islands = res.data.map(this.islandService.addDecimalCoordinate);
- return { ...res, data: islands };
- }
}
diff --git a/src/village/village.controller.ts b/src/village/village.controller.ts
index 581adb6..7e95841 100644
--- a/src/village/village.controller.ts
+++ b/src/village/village.controller.ts
@@ -27,7 +27,7 @@ import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
export class VillageController {
constructor(private readonly villageService: VillageService) {}
- @ApiOperation({ description: 'Get villages by its name.' })
+ @ApiOperation({ description: 'Get the villages.' })
@ApiQuery({
name: 'sortBy',
description: 'Sort by village code or name.',
diff --git a/test/district.e2e-spec.ts b/test/district.e2e-spec.ts
index 2f9c4e8..83586b3 100644
--- a/test/district.e2e-spec.ts
+++ b/test/district.e2e-spec.ts
@@ -1,6 +1,6 @@
-import { District, Village } from '@prisma/client';
+import { District } from '@prisma/client';
import { AppTester } from './helper/app-tester';
-import { districtRegex, villageRegex } from './helper/data-regex';
+import { districtRegex } from './helper/data-regex';
describe('District (e2e)', () => {
const baseUrl = '/districts';
@@ -105,40 +105,6 @@ describe('District (e2e)', () => {
});
});
- describe(`GET ${baseUrl}/{code}/villages`, () => {
- it('should return 400 if the `code` is invalid', async () => {
- await tester.expectBadCode(
- (code) => `${baseUrl}/${code}/villages`,
- badDistrictCodes,
- );
- });
-
- it('should return 400 if any villages sort query is invalid', async () => {
- await tester.expectBadSortQuery(
- (sortQueryStr) => `${baseUrl}?${sortQueryStr}`,
- ['', 'unknown'],
- );
- });
-
- it('should return 404 if the `code` does not exist', async () => {
- await tester.expectNotFound(`${baseUrl}/000000/villages`);
- });
-
- it('should return all villages in the district with the `code`', async () => {
- const villages = await tester.expectData(
- `${baseUrl}/${testCode}/villages`,
- );
-
- villages.forEach((village) => {
- expect(village).toEqual({
- code: expect.stringMatching(villageRegex.code),
- name: expect.stringMatching(villageRegex.name),
- districtCode: testCode,
- });
- });
- });
- });
-
afterAll(async () => {
await tester.closeApp();
});
diff --git a/test/province.e2e-spec.ts b/test/province.e2e-spec.ts
index 4382bcd..f9e1de0 100644
--- a/test/province.e2e-spec.ts
+++ b/test/province.e2e-spec.ts
@@ -1,6 +1,6 @@
-import { Province, Regency } from '@prisma/client';
+import { Province } from '@prisma/client';
import { AppTester } from './helper/app-tester';
-import { provinceRegex, regencyRegex } from './helper/data-regex';
+import { provinceRegex } from './helper/data-regex';
describe('Province (e2e)', () => {
const baseUrl = '/provinces';
@@ -89,36 +89,6 @@ describe('Province (e2e)', () => {
});
});
- describe(`GET ${baseUrl}/{code}/regencies`, () => {
- it('should return 400 if the `code` is invalid', async () => {
- await tester.expectBadCode(
- (code) => `${baseUrl}/${code}/regencies`,
- badProvinceCodes,
- );
- });
-
- it('should return 400 if regencies sort query is invalid', async () => {
- await tester.expectBadSortQuery(
- (sortQueryStr) => `${baseUrl}/${testCode}/regencies?${sortQueryStr}`,
- ['', 'unknown'],
- );
- });
-
- it('should return all regencies from specific province', async () => {
- const regencies = await tester.expectData(
- `${baseUrl}/${testCode}/regencies`,
- );
-
- regencies.forEach((regency) => {
- expect(regency).toEqual({
- code: expect.stringMatching(regencyRegex.code),
- name: expect.stringMatching(regencyRegex.name),
- provinceCode: testCode,
- });
- });
- });
- });
-
afterAll(async () => {
await tester.closeApp();
});
diff --git a/test/regency.e2e-spec.ts b/test/regency.e2e-spec.ts
index f368b5e..d239477 100644
--- a/test/regency.e2e-spec.ts
+++ b/test/regency.e2e-spec.ts
@@ -1,6 +1,6 @@
-import { District, Island, Regency } from '@prisma/client';
+import { Regency } from '@prisma/client';
import { AppTester } from './helper/app-tester';
-import { districtRegex, islandRegex, regencyRegex } from './helper/data-regex';
+import { regencyRegex } from './helper/data-regex';
describe('Regency (e2e)', () => {
const baseUrl = '/regencies';
@@ -90,81 +90,6 @@ describe('Regency (e2e)', () => {
});
});
- describe(`GET ${baseUrl}/{code}/districts`, () => {
- it('should return 400 if the `code` is invalid', async () => {
- await tester.expectBadCode(
- (code) => `${baseUrl}/${code}/districts`,
- badRegencyCodes,
- );
- });
-
- it('should return 400 if any districts sort query is invalid', async () => {
- await tester.expectBadSortQuery(
- (sortQueryStr) => `${baseUrl}?${sortQueryStr}`,
- ['', 'unknown'],
- );
- });
-
- it('should return 404 if the `code` does not match with any regency', async () => {
- await tester.expectNotFound(`${baseUrl}/0000/districts`);
- });
-
- it('should return all districts from specific regency', async () => {
- const districts = await tester.expectData(
- `${baseUrl}/${testCode}/districts`,
- );
-
- districts.forEach((district) => {
- expect(district).toEqual({
- code: expect.stringMatching(districtRegex.code),
- name: expect.stringMatching(districtRegex.name),
- regencyCode: testCode,
- });
- });
- });
- });
-
- describe(`GET ${baseUrl}/{code}/islands`, () => {
- it('should return 400 if the `code` is invalid', async () => {
- await tester.expectBadCode(
- (code) => `${baseUrl}/${code}/islands`,
- badRegencyCodes,
- );
- });
-
- it('should return 404 if the `code` does not match with any regency', async () => {
- await tester.expectNotFound(`${baseUrl}/0000/islands`);
- });
-
- it('should return all islands from specific regency', async () => {
- const testCode = '1101';
- const islands = await tester.expectData(
- `${baseUrl}/${testCode}/islands`,
- );
-
- islands.forEach((island) => {
- expect(island).toEqual({
- code: expect.stringMatching(islandRegex.code),
- coordinate: expect.stringMatching(islandRegex.coordinate),
- isOutermostSmall: expect.any(Boolean),
- isPopulated: expect.any(Boolean),
- latitude: expect.any(Number),
- longitude: expect.any(Number),
- name: expect.stringMatching(islandRegex.name),
- regencyCode: testCode,
- });
- });
- });
-
- it('should return empty array if there are no any island in the regency', async () => {
- const islands = await tester.expectData(
- `${baseUrl}/1102/islands`,
- );
-
- expect(islands).toEqual([]);
- });
- });
-
afterAll(async () => {
await tester.closeApp();
});