Skip to content

Commit

Permalink
Add query pram to filter data by the 'parent' code (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
fityannugroho authored Oct 4, 2023
2 parents 096a24f + e4633af commit 0befe93
Show file tree
Hide file tree
Showing 27 changed files with 404 additions and 123 deletions.
32 changes: 0 additions & 32 deletions src/common/common.service.ts

This file was deleted.

11 changes: 8 additions & 3 deletions src/district/__mocks__/district.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FindOptions } from '@/common/common.service';
import { sortArray } from '@/common/utils/array';
import { SortOptions } from '@/sort/sort.service';
import { District, Village } from '@prisma/client';
import { DistrictFindQueries } from '../district.dto';

export class MockDistrictService {
readonly districts: District[];
Expand All @@ -14,13 +14,18 @@ export class MockDistrictService {

async find({
name = '',
regencyCode,
sortBy = 'code',
sortOrder,
}: FindOptions<District> = {}) {
const res = this.districts.filter((district) =>
}: DistrictFindQueries = {}) {
let res = this.districts.filter((district) =>
district.name.toLowerCase().includes(name.toLowerCase()),
);

if (regencyCode) {
res = res.filter((district) => district.regencyCode === regencyCode);
}

return Promise.resolve({ data: sortArray(res, sortBy, sortOrder) });
}

Expand Down
14 changes: 14 additions & 0 deletions src/district/district.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ describe('DistrictController', () => {
),
);
});

it('should return districts filtered by regency code', async () => {
const regencyCode = '1101';
const filteredDistrictsByRegencyCode = districts.filter(
(p) => p.regencyCode === regencyCode,
);
const { data } = await controller.find({ regencyCode });

for (const district of data) {
expect(district).toEqual(expect.objectContaining({ regencyCode }));
}

expect(data).toHaveLength(filteredDistrictsByRegencyCode.length);
});
});

describe('findByCode', () => {
Expand Down
7 changes: 5 additions & 2 deletions src/district/district.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ export class District {
@IsNotEmpty()
@IsNumberString()
@Length(4, 4)
@ApiProperty({ example: '1101' })
@ApiProperty({
description: 'The regency code of the district',
example: '1101',
})
regencyCode: string;
}

Expand All @@ -37,7 +40,7 @@ export class DistrictSortQuery extends SortQuery {
}

export class DistrictFindQueries extends IntersectionType(
PartialType(PickType(District, ['name'] as const)),
PartialType(PickType(District, ['name', 'regencyCode'] as const)),
DistrictSortQuery,
PaginationQuery,
) {}
Expand Down
37 changes: 32 additions & 5 deletions src/district/district.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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' },
Expand Down Expand Up @@ -39,7 +40,7 @@ describe('DistrictService', () => {
const paginatorOptions = {
model: 'District',
paginate: { page: undefined, limit: undefined },
args: {},
args: { where: {} },
};

it('should return all districts', async () => {
Expand Down Expand Up @@ -92,12 +93,15 @@ describe('DistrictService', () => {
.spyOn(prismaService, 'paginator')
.mockResolvedValue({ data: expectedDistricts });

const result = await service.find({ sortBy: 'name', sortOrder: 'asc' });
const result = await service.find({
sortBy: 'name',
sortOrder: SortOrder.ASC,
});

expect(paginatorSpy).toHaveBeenCalledTimes(1);
expect(paginatorSpy).toHaveBeenCalledWith({
...paginatorOptions,
args: { orderBy: { name: 'asc' } },
args: { where: {}, orderBy: { name: 'asc' } },
});
expect(result.data).toEqual(expectedDistricts);
});
Expand All @@ -111,12 +115,35 @@ describe('DistrictService', () => {
.spyOn(prismaService, 'paginator')
.mockResolvedValue({ data: expectedDistricts });

const result = await service.find({ sortBy: 'name', sortOrder: 'desc' });
const result = await service.find({
sortBy: 'name',
sortOrder: SortOrder.DESC,
});

expect(paginatorSpy).toHaveBeenCalledTimes(1);
expect(paginatorSpy).toHaveBeenCalledWith({
...paginatorOptions,
args: { where: {}, orderBy: { name: 'desc' } },
});
expect(result.data).toEqual(expectedDistricts);
});

it('should return districts filtered by regency code', async () => {
const regencyCode = '1101';
const expectedDistricts = districts.filter(
(d) => d.regencyCode === regencyCode,
);

const paginatorSpy = vitest
.spyOn(prismaService, 'paginator')
.mockResolvedValue({ data: expectedDistricts });

const result = await service.find({ regencyCode });

expect(paginatorSpy).toHaveBeenCalledTimes(1);
expect(paginatorSpy).toHaveBeenCalledWith({
...paginatorOptions,
args: { orderBy: { name: 'desc' } },
args: { where: { regencyCode } },
});
expect(result.data).toEqual(expectedDistricts);
});
Expand Down
17 changes: 9 additions & 8 deletions src/district/district.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { CommonService, FindOptions } from '@/common/common.service';
import { PaginationQuery } from '@/common/dto/pagination.dto';
import { PaginatedReturn } from '@/common/interceptor/paginate.interceptor';
import { getDBProviderFeatures } from '@/common/utils/db';
Expand All @@ -7,9 +6,10 @@ import { SortOptions, SortService } from '@/sort/sort.service';
import { VillageService } from '@/village/village.service';
import { Injectable } from '@nestjs/common';
import { District, Village } from '@prisma/client';
import { DistrictFindQueries } from './district.dto';

@Injectable()
export class DistrictService implements CommonService<District> {
export class DistrictService {
readonly sorter: SortService<District>;

constructor(
Expand All @@ -23,24 +23,25 @@ export class DistrictService implements CommonService<District> {
}

async find(
options?: FindOptions<District>,
options?: DistrictFindQueries,
): Promise<PaginatedReturn<District>> {
const { name, page, limit, sortBy, sortOrder } = options ?? {};
const { name, regencyCode, page, limit, sortBy, sortOrder } = options ?? {};

return this.prisma.paginator({
model: 'District',
paginate: { page, limit },
args: {
...(name && {
where: {
where: {
...(name && {
name: {
contains: name,
...(getDBProviderFeatures()?.filtering?.insensitive && {
mode: 'insensitive',
}),
},
},
}),
}),
...(regencyCode && { regencyCode }),
},
...((sortBy || sortOrder) && {
orderBy: this.sorter.object({ sortBy, sortOrder }),
}),
Expand Down
15 changes: 12 additions & 3 deletions src/island/__mocks__/island.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FindOptions } from '@/common/common.service';
import { sortArray } from '@/common/utils/array';
import { convertCoordinate } from '@/common/utils/coordinate';
import { Island } from '@prisma/client';
import { IslandFindQueries } from '../island.dto';

export class MockIslandService {
readonly islands: Island[];
Expand All @@ -18,13 +18,22 @@ export class MockIslandService {

async find({
name = '',
regencyCode,
sortBy = 'code',
sortOrder,
}: FindOptions<Island> = {}) {
const res = this.islands.filter((island) =>
}: IslandFindQueries = {}) {
let res = this.islands.filter((island) =>
island.name.toLowerCase().includes(name.toLowerCase()),
);

if (typeof regencyCode === 'string') {
res = res.filter((island) =>
regencyCode === ''
? island.regencyCode === null
: island.regencyCode === regencyCode,
);
}

return Promise.resolve({ data: sortArray(res, sortBy, sortOrder) });
}

Expand Down
25 changes: 25 additions & 0 deletions src/island/island.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,31 @@ describe('IslandController', () => {
),
);
});

it('should return islands filtered by regencyCode', async () => {
const regencyCode = '1101';
const { data } = await controller.find({ regencyCode });

for (const island of data) {
expect(island).toEqual(expect.objectContaining({ regencyCode }));
}

expect(data).toHaveLength(
islands.filter((island) => island.regencyCode === regencyCode).length,
);
});

it('should return islands that does not belong to any regency', async () => {
const { data } = await controller.find({ regencyCode: '' });

for (const island of data) {
expect(island).toEqual(expect.objectContaining({ regencyCode: null }));
}

expect(data).toHaveLength(
islands.filter((island) => island.regencyCode === null).length,
);
});
});

describe('findByCode', () => {
Expand Down
10 changes: 8 additions & 2 deletions src/island/island.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
IsOptional,
IsString,
Length,
ValidateIf,
} from 'class-validator';
import { EqualsAny } from '../common/decorator/EqualsAny';
import { IsNotSymbol } from '../common/decorator/IsNotSymbol';
Expand Down Expand Up @@ -49,10 +50,15 @@ export class Island {
})
name: string;

@ValidateIf((o) => o.regencyCode)
@IsOptional()
@IsNumberString()
@Length(4, 4)
@ApiProperty({ example: '1101' })
@ApiProperty({
description: `The regency code of the island.
Providing an empty string will filter islands that are not part of any regency.`,
example: '1101',
})
regencyCode?: string;

@ApiProperty({ example: 3.317622222222222 })
Expand All @@ -68,7 +74,7 @@ export class IslandSortQuery extends SortQuery {
}

export class IslandFindQueries extends IntersectionType(
PartialType(PickType(Island, ['name'] as const)),
PartialType(PickType(Island, ['name', 'regencyCode'] as const)),
IslandSortQuery,
PaginationQuery,
) {}
Expand Down
Loading

0 comments on commit 0befe93

Please sign in to comment.