Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/server-nestjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
"@ts-rest/fastify": "^3.52.1",
"@ts-rest/open-api": "^3.52.1",
"axios": "^1.13.6",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.4",
"date-fns": "^4.1.0",
"dotenv": "^16.6.1",
"fastify": "^4.29.1",
Expand Down
2 changes: 2 additions & 0 deletions apps/server-nestjs/src/main.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { ScheduleModule } from '@nestjs/schedule'
import { CpinModule } from './cpin-module/cpin.module'
import { HealthzModule } from './modules/healthz/healthz.module'
import { KeycloakModule } from './modules/keycloak/keycloak.module'
import { SystemSettingsModule } from './modules/system-settings/system-settings.module'
import { VersionModule } from './modules/version/version.module'

@Module({
imports: [
CpinModule,
KeycloakModule,
HealthzModule,
SystemSettingsModule,
VersionModule,
EventEmitterModule.forRoot(),
ScheduleModule.forRoot(),
Expand Down
2 changes: 2 additions & 0 deletions apps/server-nestjs/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ValidationPipe } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import { Logger } from 'nestjs-pino'
import { ConfigurationService } from './cpin-module/infrastructure/configuration/configuration.service'
Expand All @@ -6,6 +7,7 @@ import { MainModule } from './main.module'
async function bootstrap() {
const app = await NestFactory.create(MainModule, { bufferLogs: true })
app.useLogger(app.get(Logger))
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }))
app.flushLogs()
app.enableShutdownHooks()
const config = app.get(ConfigurationService)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsOptional, IsString } from 'class-validator'

export class ListSystemSettingsQueryDto {
@IsString()
@IsOptional()
key?: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { IsNotEmpty, IsString } from 'class-validator'

export class SystemSettingDto {
@IsString()
@IsNotEmpty()
value!: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ListSystemSettingsQueryDto } from './dto/list-system-settings-query.dto'
import type { SystemSettingDto } from './dto/system-setting.dto'
import { Body, Controller, Get, Inject, Param, Put, Query } from '@nestjs/common'
import { SystemSettingsService } from './system-settings.service'

@Controller('api/v1/system/settings')
export class SystemSettingsController {
constructor(@Inject(SystemSettingsService) private readonly service: SystemSettingsService) {}

@Get()
async list(
@Query() query: ListSystemSettingsQueryDto,
) {
return this.service.list(query.key)
}

@Put(':key')
async upsert(
@Param('key') key: string,
@Body() body: SystemSettingDto,
) {
return this.service.upsert(key, body)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common'
import { InfrastructureModule } from '../../cpin-module/infrastructure/infrastructure.module'
import { SystemSettingsController } from './system-settings.controller'
import { SystemSettingsService } from './system-settings.service'

@Module({
imports: [InfrastructureModule],
controllers: [SystemSettingsController],
providers: [SystemSettingsService],
exports: [SystemSettingsService],
})
export class SystemSettingsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { SystemSetting } from '@prisma/client'
import type { SystemSettingDto } from './dto/system-setting.dto'
import { Inject, Injectable } from '@nestjs/common'
import { PrismaService } from '../../cpin-module/infrastructure/database/prisma.service'

@Injectable()
export class SystemSettingsService {
constructor(@Inject(PrismaService) private readonly prisma: PrismaService) {}

async list(key?: string): Promise<SystemSetting[]> {
return this.prisma.systemSetting.findMany({ where: { key } })
}

async upsert(key: string, systemSetting: SystemSettingDto): Promise<SystemSetting> {
return this.prisma.systemSetting.upsert({
create: { key, ...systemSetting },
update: systemSetting,
where: { key },
})
}
}
120 changes: 120 additions & 0 deletions apps/server-nestjs/test/system-settings.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import type { INestApplication } from '@nestjs/common'
import { faker } from '@faker-js/faker'
import { ValidationPipe } from '@nestjs/common'
import { Test } from '@nestjs/testing'
import request from 'supertest'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { PrismaService } from '../src/cpin-module/infrastructure/database/prisma.service'
import { SystemSettingsModule } from '../src/modules/system-settings/system-settings.module'

const canRunSystemSettingsE2E = Boolean(process.env.E2E)

const describeWithSystemSettings = describe.runIf(canRunSystemSettingsE2E)

describeWithSystemSettings('systemSettingsController (e2e)', () => {
let app: INestApplication
let prisma: PrismaService
const headerUserId = 'x-test-user-id'

let testUserId: string
let testSystemSettingKey: string

beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [SystemSettingsModule],
}).compile()

app = moduleRef.createNestApplication()
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }))
await app.init()

prisma = app.get(PrismaService)
await prisma.$connect()

testUserId = faker.string.uuid()
testSystemSettingKey = faker.lorem.slug()

await prisma.user.create({
data: {
id: testUserId,
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
email: faker.internet.email(),
type: 'human',
adminRoleIds: [],
},
})

await prisma.systemSetting.upsert({
where: { key: testSystemSettingKey },
create: { key: testSystemSettingKey, value: faker.lorem.word() },
update: { value: faker.lorem.word() },
})
})

afterAll(async () => {
await prisma.systemSetting.deleteMany({ where: { key: testSystemSettingKey } })
await prisma.user.deleteMany({ where: { id: testUserId } })
await prisma.$disconnect()
await app.close()
})

it('read', async () => {
const userId = faker.string.uuid()
await prisma.user.create({
data: {
id: userId,
firstName: 'Test',
lastName: 'User',
email: `user-${userId}@test.local`,
type: 'human',
},
})
const key = `e2e-system-setting-${faker.string.uuid()}`
await prisma.systemSetting.upsert({
where: { key },
create: { key, value: 'bar' },
update: { value: 'bar' },
})

await request(app.getHttpServer())
.get(`/api/v1/system/settings?key=${encodeURIComponent(key)}`)
.set(headerUserId, userId)
.expect(200)
.expect(({ body }) => {
expect(body).toEqual([expect.objectContaining({ key, value: 'bar' })])
})

await prisma.systemSetting.delete({ where: { key } })
await prisma.user.delete({ where: { id: userId } })
})

it('upserts', async () => {
const userId = faker.string.uuid()
await prisma.user.create({
data: {
id: userId,
firstName: 'Test',
lastName: 'User',
email: `user-${userId}@test.local`,
type: 'human',
adminRoleIds: [],
},
})
const key = `e2e-system-setting-${faker.string.uuid()}`
await request(app.getHttpServer())
.put(`/api/v1/system/settings/${encodeURIComponent(key)}`)
.set(headerUserId, userId)
.send({ value: 'bar' })
.expect(200)
.expect(({ body }) => {
expect(body).toEqual(expect.objectContaining({ key, value: 'bar' }))
})

const saved = await prisma.systemSetting.findUnique({ where: { key } })
expect(saved).toEqual(expect.objectContaining({ key, value: 'bar' }))

await prisma.systemSetting.delete({ where: { key } })
await prisma.user.delete({ where: { id: userId } })
})
})
Loading
Loading