From fc37819dfc5c9b67b79107410fc597d839ab2276 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Thu, 22 Jun 2023 16:19:28 -0300 Subject: [PATCH 1/3] feat: add info route --- src/app.module.ts | 2 ++ .../migrations/1687403553617-CreateInfo.ts | 15 ++++++++ src/database/seeds/info/info-data.ts | 6 ++++ src/database/seeds/info/info-seed.module.ts | 11 ++++++ src/database/seeds/info/info-seed.service.ts | 35 +++++++++++++++++++ src/database/seeds/run-seed.ts | 2 ++ src/database/seeds/seed.module.ts | 2 ++ src/info/entities/info.entity.ts | 19 ++++++++++ src/info/info-data.interface.ts | 5 +++ src/info/info.controller.spec.ts | 18 ++++++++++ src/info/info.controller.ts | 18 ++++++++++ src/info/info.module.ts | 12 +++++++ src/info/info.service.spec.ts | 18 ++++++++++ src/info/info.service.ts | 16 +++++++++ 14 files changed, 179 insertions(+) create mode 100644 src/database/migrations/1687403553617-CreateInfo.ts create mode 100644 src/database/seeds/info/info-data.ts create mode 100644 src/database/seeds/info/info-seed.module.ts create mode 100644 src/database/seeds/info/info-seed.service.ts create mode 100644 src/info/entities/info.entity.ts create mode 100644 src/info/info-data.interface.ts create mode 100644 src/info/info.controller.spec.ts create mode 100644 src/info/info.controller.ts create mode 100644 src/info/info.module.ts create mode 100644 src/info/info.service.spec.ts create mode 100644 src/info/info.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index d5ee03dc..342e79c8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -28,6 +28,7 @@ import { MailModule } from './mail/mail.module'; import { HomeModule } from './home/home.module'; import { DataSource, DataSourceOptions } from 'typeorm'; import { AllConfigType } from './config/config.type'; +import { InfoModule } from './info/info.module'; @Module({ imports: [ @@ -84,6 +85,7 @@ import { AllConfigType } from './config/config.type'; ForgotModule, MailModule, HomeModule, + InfoModule, ], }) export class AppModule {} diff --git a/src/database/migrations/1687403553617-CreateInfo.ts b/src/database/migrations/1687403553617-CreateInfo.ts new file mode 100644 index 00000000..7a059d53 --- /dev/null +++ b/src/database/migrations/1687403553617-CreateInfo.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateInfo1687403553617 implements MigrationInterface { + name = 'CreateInfo1687403553617'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "info" ("id" integer NOT NULL, "name" character varying NOT NULL, "value" character varying NOT NULL, CONSTRAINT "UQ_916df6cf672c24f99ceab946a88" UNIQUE ("name"), CONSTRAINT "PK_687dc5e25f4f1ee093a45b68bb7" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "info"`); + } +} diff --git a/src/database/seeds/info/info-data.ts b/src/database/seeds/info/info-data.ts new file mode 100644 index 00000000..bd5eadd0 --- /dev/null +++ b/src/database/seeds/info/info-data.ts @@ -0,0 +1,6 @@ +import { InfoData } from 'src/info/info-data.interface'; + +export const infoData: InfoData[] = [ + { name: 'version', value: '1' }, + { name: 'sampleSetting', value: 'false' }, +]; diff --git a/src/database/seeds/info/info-seed.module.ts b/src/database/seeds/info/info-seed.module.ts new file mode 100644 index 00000000..d819dabe --- /dev/null +++ b/src/database/seeds/info/info-seed.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { InfoSeedService } from './info-seed.service'; +import { Info } from 'src/info/entities/info.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Info])], + providers: [InfoSeedService], + exports: [InfoSeedService], +}) +export class InfoSeedModule {} diff --git a/src/database/seeds/info/info-seed.service.ts b/src/database/seeds/info/info-seed.service.ts new file mode 100644 index 00000000..9a69186f --- /dev/null +++ b/src/database/seeds/info/info-seed.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Info } from 'src/info/entities/info.entity'; +import { Repository } from 'typeorm'; +import { infoData } from './info-data'; + +@Injectable() +export class InfoSeedService { + constructor( + @InjectRepository(Info) + private repository: Repository, + ) {} + + async run() { + let id = 1; + for (const item of infoData) { + const count = await this.repository.count({ + where: { + name: item.name, + }, + }); + + if (!count) { + await this.repository.save( + this.repository.create({ + id: id, + name: item.name, + value: item.value, + }), + ); + } + id++; + } + } +} diff --git a/src/database/seeds/run-seed.ts b/src/database/seeds/run-seed.ts index e0860129..638e1a67 100644 --- a/src/database/seeds/run-seed.ts +++ b/src/database/seeds/run-seed.ts @@ -3,6 +3,7 @@ import { RoleSeedService } from './role/role-seed.service'; import { SeedModule } from './seed.module'; import { StatusSeedService } from './status/status-seed.service'; import { UserSeedService } from './user/user-seed.service'; +import { InfoSeedService } from './info/info-seed.service'; const runSeed = async () => { const app = await NestFactory.create(SeedModule); @@ -11,6 +12,7 @@ const runSeed = async () => { await app.get(RoleSeedService).run(); await app.get(StatusSeedService).run(); await app.get(UserSeedService).run(); + await app.get(InfoSeedService).run(); await app.close(); }; diff --git a/src/database/seeds/seed.module.ts b/src/database/seeds/seed.module.ts index cfb415fa..d1c29b26 100644 --- a/src/database/seeds/seed.module.ts +++ b/src/database/seeds/seed.module.ts @@ -8,12 +8,14 @@ import { TypeOrmConfigService } from '../typeorm-config.service'; import { RoleSeedModule } from './role/role-seed.module'; import { StatusSeedModule } from './status/status-seed.module'; import { UserSeedModule } from './user/user-seed.module'; +import { InfoSeedModule } from './info/info-seed.module'; @Module({ imports: [ RoleSeedModule, StatusSeedModule, UserSeedModule, + InfoSeedModule, ConfigModule.forRoot({ isGlobal: true, load: [databaseConfig, appConfig], diff --git a/src/info/entities/info.entity.ts b/src/info/entities/info.entity.ts new file mode 100644 index 00000000..610b414e --- /dev/null +++ b/src/info/entities/info.entity.ts @@ -0,0 +1,19 @@ +import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; + +@Entity({ name: 'info' }) +export class Info extends BaseEntity { + @Exclude() + @ApiProperty({ example: 1 }) + @PrimaryColumn() + id: number; + + @ApiProperty({ example: 'version' }) + @Column({ unique: true }) + name: string; + + @ApiProperty({ example: '1' }) + @Column() + value: string; +} diff --git a/src/info/info-data.interface.ts b/src/info/info-data.interface.ts new file mode 100644 index 00000000..ab85bdb4 --- /dev/null +++ b/src/info/info-data.interface.ts @@ -0,0 +1,5 @@ +// info-data.interface.ts +export interface InfoData { + name: string; + value: string; +} diff --git a/src/info/info.controller.spec.ts b/src/info/info.controller.spec.ts new file mode 100644 index 00000000..9e819b93 --- /dev/null +++ b/src/info/info.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { InfoController } from './info.controller'; + +describe('InfoController', () => { + let controller: InfoController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [InfoController], + }).compile(); + + controller = module.get(InfoController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/info/info.controller.ts b/src/info/info.controller.ts new file mode 100644 index 00000000..5207dce3 --- /dev/null +++ b/src/info/info.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { InfoService } from './info.service'; +import { Info } from './entities/info.entity'; + +@ApiTags('Info') +@Controller({ + path: 'info', + version: '1', +}) +export class InfoController { + constructor(private readonly infoService: InfoService) {} + + @Get() + async getAll(): Promise { + return this.infoService.getAll(); + } +} diff --git a/src/info/info.module.ts b/src/info/info.module.ts new file mode 100644 index 00000000..112def9c --- /dev/null +++ b/src/info/info.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { InfoController } from './info.controller'; +import { InfoService } from './info.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Info } from './entities/info.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Info])], + controllers: [InfoController], + providers: [InfoService], +}) +export class InfoModule {} diff --git a/src/info/info.service.spec.ts b/src/info/info.service.spec.ts new file mode 100644 index 00000000..12e5e085 --- /dev/null +++ b/src/info/info.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { InfoService } from './info.service'; + +describe('InfoService', () => { + let service: InfoService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [InfoService], + }).compile(); + + service = module.get(InfoService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/info/info.service.ts b/src/info/info.service.ts new file mode 100644 index 00000000..04509eb4 --- /dev/null +++ b/src/info/info.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { Info } from './entities/info.entity'; +import { InjectRepository } from '@nestjs/typeorm'; + +@Injectable() +export class InfoService { + constructor( + @InjectRepository(Info) + private readonly infoRepository: Repository, + ) {} + + async getAll(): Promise { + return this.infoRepository.find(); + } +} From 1c8e07022e1e6a85634d0331d06fcab5b79736b0 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 23 Jun 2023 13:44:01 -0300 Subject: [PATCH 2/3] feat: Add version column to info endpoint Added new column "version" to database. --- .../1687533339069-ColumnInfoVersion.ts | 15 +++++++++++ src/database/seeds/info/info-data.ts | 8 ++++-- src/database/seeds/info/info-seed.service.ts | 2 ++ src/info/entities/info.entity.ts | 4 +++ src/info/info-data.interface.ts | 1 + src/info/info.controller.ts | 22 ++++++++++------ src/info/info.service.ts | 26 ++++++++++++++++--- 7 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 src/database/migrations/1687533339069-ColumnInfoVersion.ts diff --git a/src/database/migrations/1687533339069-ColumnInfoVersion.ts b/src/database/migrations/1687533339069-ColumnInfoVersion.ts new file mode 100644 index 00000000..87828cd8 --- /dev/null +++ b/src/database/migrations/1687533339069-ColumnInfoVersion.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ColumnInfoVersion1687533339069 implements MigrationInterface { + name = 'ColumnInfoVersion1687533339069'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "info" ADD "version" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "info" DROP COLUMN "version"`); + } +} diff --git a/src/database/seeds/info/info-data.ts b/src/database/seeds/info/info-data.ts index bd5eadd0..a9836dc2 100644 --- a/src/database/seeds/info/info-data.ts +++ b/src/database/seeds/info/info-data.ts @@ -1,6 +1,10 @@ import { InfoData } from 'src/info/info-data.interface'; export const infoData: InfoData[] = [ - { name: 'version', value: '1' }, - { name: 'sampleSetting', value: 'false' }, + // settings for any api version + { name: 'maintenance', value: 'false' }, + + // v1 + { name: 'abTestEnabled', value: 'false', version: '1' }, + { name: 'maxUploadSize', value: '10MB', version: '1' }, ]; diff --git a/src/database/seeds/info/info-seed.service.ts b/src/database/seeds/info/info-seed.service.ts index 9a69186f..7d183274 100644 --- a/src/database/seeds/info/info-seed.service.ts +++ b/src/database/seeds/info/info-seed.service.ts @@ -17,6 +17,7 @@ export class InfoSeedService { const count = await this.repository.count({ where: { name: item.name, + version: item.version, }, }); @@ -26,6 +27,7 @@ export class InfoSeedService { id: id, name: item.name, value: item.value, + version: item.version, }), ); } diff --git a/src/info/entities/info.entity.ts b/src/info/entities/info.entity.ts index 610b414e..831efb7e 100644 --- a/src/info/entities/info.entity.ts +++ b/src/info/entities/info.entity.ts @@ -16,4 +16,8 @@ export class Info extends BaseEntity { @ApiProperty({ example: '1' }) @Column() value: string; + + @ApiProperty({ example: '1' }) + @Column({ nullable: true }) + version: string; } diff --git a/src/info/info-data.interface.ts b/src/info/info-data.interface.ts index ab85bdb4..d8984283 100644 --- a/src/info/info-data.interface.ts +++ b/src/info/info-data.interface.ts @@ -2,4 +2,5 @@ export interface InfoData { name: string; value: string; + version?: string; } diff --git a/src/info/info.controller.ts b/src/info/info.controller.ts index 5207dce3..5bbefa88 100644 --- a/src/info/info.controller.ts +++ b/src/info/info.controller.ts @@ -1,18 +1,24 @@ -import { Controller, Get } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiTags, ApiParam } from '@nestjs/swagger'; import { InfoService } from './info.service'; import { Info } from './entities/info.entity'; +import { NullableType } from 'src/utils/types/nullable.type'; @ApiTags('Info') -@Controller({ - path: 'info', - version: '1', -}) +@Controller('info') export class InfoController { constructor(private readonly infoService: InfoService) {} @Get() - async getAll(): Promise { - return this.infoService.getAll(); + async getAll(): Promise> { + return this.infoService.find(); + } + + @Get('v:version') + @ApiParam({ name: 'version', example: '1' }) // Add this line + getByVersion( + @Param('version') version: string, + ): Promise> { + return this.infoService.findByVersion(version); } } diff --git a/src/info/info.service.ts b/src/info/info.service.ts index 04509eb4..7c4c1f44 100644 --- a/src/info/info.service.ts +++ b/src/info/info.service.ts @@ -1,7 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { Repository } from 'typeorm'; +import { IsNull, Repository } from 'typeorm'; import { Info } from './entities/info.entity'; import { InjectRepository } from '@nestjs/typeorm'; +import { EntityCondition } from 'src/utils/types/entity-condition.type'; +import { NullableType } from 'src/utils/types/nullable.type'; @Injectable() export class InfoService { @@ -10,7 +12,25 @@ export class InfoService { private readonly infoRepository: Repository, ) {} - async getAll(): Promise { - return this.infoRepository.find(); + async find(fields?: EntityCondition): Promise> { + return this.infoRepository.find({ + where: fields, + }); + } + + async findByVersion(version: string): Promise { + // If no version found, return empty to indicate it + const count = await this.infoRepository.count({ + where: { version: version }, + }); + + if (count === 0) { + return []; + } + + // Otherwise, return settings for given version and for any version (null) + return this.infoRepository.find({ + where: [{ version: version }, { version: IsNull() }], + }); } } From 45e1d626bac716e8f4fbfa5c9ff1dbd7b970b081 Mon Sep 17 00:00:00 2001 From: Raphael Rivas Date: Fri, 23 Jun 2023 14:56:17 -0300 Subject: [PATCH 3/3] style: clear comment --- src/info/info.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/info/info.controller.ts b/src/info/info.controller.ts index 5bbefa88..86472295 100644 --- a/src/info/info.controller.ts +++ b/src/info/info.controller.ts @@ -15,7 +15,7 @@ export class InfoController { } @Get('v:version') - @ApiParam({ name: 'version', example: '1' }) // Add this line + @ApiParam({ name: 'version', example: '1' }) getByVersion( @Param('version') version: string, ): Promise> {