From 9b2b7e298ee63ae8b27d21e2e0aa74c513e1bb0b Mon Sep 17 00:00:00 2001 From: Fityan <63894003+fityannugroho@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:41:09 +0700 Subject: [PATCH 1/3] chore: enable strict null checks in TypeScript configuration --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index a4be4f9..b2b6e6a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,7 @@ "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": false, + "strictNullChecks": true, "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, From a8d321d8a64c893a9271fc3b8c1671277d5ae936 Mon Sep 17 00:00:00 2001 From: Fityan <63894003+fityannugroho@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:41:57 +0700 Subject: [PATCH 2/3] fix: enhance error handling and type safety in various modules --- prisma/seeder.ts | 6 ++++++ src/app.module.ts | 4 ++-- src/common/config/app.ts | 12 +++++++----- src/common/decorator/EqualsAny.ts | 6 +++--- src/common/decorator/IsNotSymbol.ts | 4 ++-- .../decorator/api-data-response.decorator.ts | 4 +--- src/common/utils/coordinate.ts | 14 +++++++++----- src/common/utils/db/index.ts | 4 ++++ src/district/district.service.spec.ts | 4 ++-- src/island/island.service.spec.ts | 10 +++++----- src/island/island.service.ts | 4 ++-- src/main.ts | 2 +- src/province/province.service.spec.ts | 3 ++- 13 files changed, 46 insertions(+), 31 deletions(-) diff --git a/prisma/seeder.ts b/prisma/seeder.ts index 0b94862..4590809 100644 --- a/prisma/seeder.ts +++ b/prisma/seeder.ts @@ -67,6 +67,12 @@ export class Seeder { async generateLog(): Promise { const packageVersion = await getInstalledPackageVersion('idn-area-data'); + if (!packageVersion) { + throw new Error( + 'idn-area-data package is not installed. Make sure to run `pnpm install` first.', + ); + } + await this.prisma.seederLogs.create({ data: { dataVersion: packageVersion }, }); diff --git a/src/app.module.ts b/src/app.module.ts index 8bac993..4240320 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,8 +22,8 @@ import { VillageModule } from './village/village.module'; inject: [ConfigService], useFactory: (config: ConfigService) => [ { - ttl: seconds(config.get('APP_THROTTLE_TTL')), - limit: config.get('APP_THROTTLE_LIMIT'), + ttl: seconds(config.get('APP_THROTTLE_TTL') as number), + limit: config.get('APP_THROTTLE_LIMIT') as number, skipIf: () => config.get('APP_ENABLE_THROTTLE') !== 'true', }, ], diff --git a/src/common/config/app.ts b/src/common/config/app.ts index 8be7bc1..3837632 100644 --- a/src/common/config/app.ts +++ b/src/common/config/app.ts @@ -27,11 +27,13 @@ export type AppConfig = { export const appConfig: AppConfig = { env: (process.env.APP_ENV as AppConfig['env']) || 'dev', host: process.env.APP_HOST || '0.0.0.0', - port: Number.parseInt(process.env.APP_PORT) || 3000, + port: Number.parseInt(process.env.APP_PORT || '3000'), pagination: { - maxPageSize: - Number.parseInt(process.env.APP_PAGINATION_MAX_PAGE_SIZE) || 100, - defaultPageSize: - Number.parseInt(process.env.APP_PAGINATION_DEFAULT_PAGE_SIZE) || 10, + maxPageSize: Number.parseInt( + process.env.APP_PAGINATION_MAX_PAGE_SIZE || '100', + ), + defaultPageSize: Number.parseInt( + process.env.APP_PAGINATION_DEFAULT_PAGE_SIZE || '10', + ), }, } as const; diff --git a/src/common/decorator/EqualsAny.ts b/src/common/decorator/EqualsAny.ts index e23a0fd..1db1d34 100644 --- a/src/common/decorator/EqualsAny.ts +++ b/src/common/decorator/EqualsAny.ts @@ -29,14 +29,14 @@ export function EqualsAny( @ValidatorConstraint({ name: 'equalsAny' }) export class EqualsAnyConstraint implements ValidatorConstraintInterface { validate(value: any, args?: ValidationArguments): boolean { - const [validValues] = args.constraints as [string[], any]; + const [validValues] = (args?.constraints ?? []) as [string[], any]; return typeof value === 'string' && validValues.includes(value); } defaultMessage(validationArguments?: ValidationArguments): string { - const { constraints, property } = validationArguments; + const { constraints, property } = validationArguments ?? {}; return `${property} must equals one of these: ${( - constraints[0] as string[] + (constraints?.[0] as string[] | undefined) ?? [] ).join(', ')}.`; } } diff --git a/src/common/decorator/IsNotSymbol.ts b/src/common/decorator/IsNotSymbol.ts index 739d96d..145a691 100644 --- a/src/common/decorator/IsNotSymbol.ts +++ b/src/common/decorator/IsNotSymbol.ts @@ -31,7 +31,7 @@ export function IsNotSymbol( @ValidatorConstraint({ name: 'isNotSymbol' }) export class IsNotSymbolConstraint implements ValidatorConstraintInterface { validate(value: any, args?: ValidationArguments): boolean { - const [allowedSymbols = ''] = args.constraints as [string, any]; + const [allowedSymbols = ''] = (args?.constraints ?? []) as [string, any]; const safeAllowedSymbols = allowedSymbols .split('') .map((s) => `\\${s}`) @@ -42,7 +42,7 @@ export class IsNotSymbolConstraint implements ValidatorConstraintInterface { } defaultMessage(validationArguments?: ValidationArguments): string { - const { constraints, property } = validationArguments; + const { constraints, property } = validationArguments ?? {}; const [allowedSymbols = ''] = constraints as [string, any]; return allowedSymbols diff --git a/src/common/decorator/api-data-response.decorator.ts b/src/common/decorator/api-data-response.decorator.ts index db78812..a84dc01 100644 --- a/src/common/decorator/api-data-response.decorator.ts +++ b/src/common/decorator/api-data-response.decorator.ts @@ -72,9 +72,7 @@ export const ApiDataResponse = >( type: 'object', nullable: options.multiple ? undefined : true, properties: { - total: options.multiple - ? { type: 'number', example: 1 } - : undefined, + total: options.multiple ? { type: 'number', example: 1 } : {}, }, }, }, diff --git a/src/common/utils/coordinate.ts b/src/common/utils/coordinate.ts index 1f919cf..9e60a7c 100644 --- a/src/common/utils/coordinate.ts +++ b/src/common/utils/coordinate.ts @@ -44,11 +44,15 @@ export const convertCoordinate = (coordinate: string): number[] => { throw new Error('Invalid coordinate format'); } - const [a, b, c, d, e, f] = coordinate.match(/[0-9,\.]+/g); - const [y, x] = coordinate.match(/(N|S|E|W)/g); + const degress = coordinate.match(/[0-9,\.]+/g); + const polars = coordinate.match(/(N|S|E|W)/g); - const latitude = calculate(a, b, c, y); - const longitude = calculate(d, e, f, x); + if (!degress || !polars) { + throw new Error('Invalid coordinate format'); + } + + const [a, b, c, d, e, f] = degress; + const [y, x] = polars; - return [latitude, longitude]; + return [calculate(a, b, c, y), calculate(d, e, f, x)]; // [latitude, longitude] }; diff --git a/src/common/utils/db/index.ts b/src/common/utils/db/index.ts index b151406..e109850 100644 --- a/src/common/utils/db/index.ts +++ b/src/common/utils/db/index.ts @@ -36,5 +36,9 @@ export const validateDBConfig = (...vars: (keyof typeof dbConfig)[]) => { * Get the database provider features, based on the current config. */ export const getDBProviderFeatures = (): DBProviderFeatures | undefined => { + if (!dbConfig.provider) { + return undefined; + } + return dbProviderConfig[dbConfig.provider]?.features; }; diff --git a/src/district/district.service.spec.ts b/src/district/district.service.spec.ts index 0de023d..42856ed 100644 --- a/src/district/district.service.spec.ts +++ b/src/district/district.service.spec.ts @@ -173,10 +173,10 @@ describe('DistrictService', () => { const testCode = '110101'; const expectedDistrict = districts.find((d) => d.code === testCode); const expectedRegency = regencies.find( - (r) => r.code === expectedDistrict.regencyCode, + (r) => r.code === expectedDistrict?.regencyCode, ); const expectedProvince = provinces.find( - (p) => p.code === expectedRegency.provinceCode, + (p) => p.code === expectedRegency?.provinceCode, ); const findUniqueSpy = vitest diff --git a/src/island/island.service.spec.ts b/src/island/island.service.spec.ts index 50c7c19..da2b486 100644 --- a/src/island/island.service.spec.ts +++ b/src/island/island.service.spec.ts @@ -223,12 +223,12 @@ describe('IslandService', () => { describe('findByCode', () => { it('should return an island', async () => { const testCode = '110140001'; - const expectedIsland = islands.find((i) => i.code === testCode); + const expectedIsland = islands.find((i) => i.code === testCode) as Island; const expectedRegency = regencies.find( - (r) => r.code === expectedIsland.regencyCode, + (r) => r.code === expectedIsland?.regencyCode, ); const expectedProvince = provinces.find( - (p) => p.code === expectedRegency.provinceCode, + (p) => p.code === expectedRegency?.provinceCode, ); const findUniqueSpy = vitest @@ -259,10 +259,10 @@ describe('IslandService', () => { it('should return an island without regency', async () => { const testCode = '120040001'; - const expectedIsland = islands.find((i) => i.code === testCode); + const expectedIsland = islands.find((i) => i.code === testCode) as Island; const expectedProvince = provinces.find( (p) => p.code === testCode.slice(0, 2), - ); + ) as Province; const findUniqueIslandSpy = vitest .spyOn(prismaService.island, 'findUnique') diff --git a/src/island/island.service.ts b/src/island/island.service.ts index b786f30..1c3b360 100644 --- a/src/island/island.service.ts +++ b/src/island/island.service.ts @@ -81,11 +81,11 @@ export class IslandService { ...this.addDecimalCoordinate(island), parent: { regency: null, - province: await this.prisma.province.findUnique({ + province: (await this.prisma.province.findUnique({ where: { code: code.slice(0, 2), }, - }), + })) as NonNullable, }, }; } diff --git a/src/main.ts b/src/main.ts index 647b9fe..7a9b1f3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,7 +18,7 @@ async function bootstrap() { // Init API documentation with Swagger. const docConfig = new DocumentBuilder() .setTitle('Indonesia Area API') - .setVersion(process.env.npm_package_version) + .setVersion(process.env.npm_package_version as string) .setDescription( 'API that provides information about Indonesia administrative area.', ) diff --git a/src/province/province.service.spec.ts b/src/province/province.service.spec.ts index 683d99d..e40d101 100644 --- a/src/province/province.service.spec.ts +++ b/src/province/province.service.spec.ts @@ -130,7 +130,8 @@ describe('ProvinceService', () => { describe('findByCode', () => { it('should return a province when given a valid code', async () => { const testCode = '11'; - const expectedProvince = provinces.find((p) => p.code === testCode); + const expectedProvince = + provinces.find((p) => p.code === testCode) ?? null; const findUniqueSpy = vitest .spyOn(prismaService.province, 'findUnique') From 3aaae3c749c71b395b51b6c560d790c2131762fd Mon Sep 17 00:00:00 2001 From: Fityan <63894003+fityannugroho@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:50:00 +0700 Subject: [PATCH 3/3] fix: correct variable name from 'degress' to 'degrees' in coordinate utility --- src/common/utils/coordinate.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/utils/coordinate.ts b/src/common/utils/coordinate.ts index 9e60a7c..f887695 100644 --- a/src/common/utils/coordinate.ts +++ b/src/common/utils/coordinate.ts @@ -44,14 +44,14 @@ export const convertCoordinate = (coordinate: string): number[] => { throw new Error('Invalid coordinate format'); } - const degress = coordinate.match(/[0-9,\.]+/g); + const degrees = coordinate.match(/[0-9,\.]+/g); const polars = coordinate.match(/(N|S|E|W)/g); - if (!degress || !polars) { + if (!degrees || !polars) { throw new Error('Invalid coordinate format'); } - const [a, b, c, d, e, f] = degress; + const [a, b, c, d, e, f] = degrees; const [y, x] = polars; return [calculate(a, b, c, y), calculate(d, e, f, x)]; // [latitude, longitude]