Skip to content

Commit d50e1af

Browse files
committed
Add prisma and swagger
1 parent c2a572f commit d50e1af

17 files changed

+435
-65
lines changed

.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

eslint.config.mjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ export default tseslint.config(
2929
rules: {
3030
'@typescript-eslint/no-explicit-any': 'off',
3131
'@typescript-eslint/no-floating-promises': 'warn',
32-
'@typescript-eslint/no-unsafe-argument': 'warn'
32+
'@typescript-eslint/no-unsafe-argument': 'off',
33+
'@typescript-eslint/no-unsafe-assignment': 'off',
34+
'@typescript-eslint/no-unsafe-call': 'off',
35+
'@typescript-eslint/no-unsafe-member-access': 'off',
3336
},
3437
},
35-
);
38+
);

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
"@nestjs/common": "^11.0.1",
2424
"@nestjs/core": "^11.0.1",
2525
"@nestjs/platform-express": "^11.0.1",
26+
"@nestjs/swagger": "^11.0.3",
27+
"@prisma/client": "^6.3.1",
28+
"class-transformer": "^0.5.1",
29+
"class-validator": "^0.14.1",
30+
"cors": "^2.8.5",
2631
"reflect-metadata": "^0.2.2",
2732
"rxjs": "^7.8.1"
2833
},
@@ -44,6 +49,7 @@
4449
"globals": "^15.14.0",
4550
"jest": "^29.7.0",
4651
"prettier": "^3.4.2",
52+
"prisma": "^6.3.1",
4753
"source-map-support": "^0.5.21",
4854
"supertest": "^7.0.0",
4955
"ts-jest": "^29.2.5",

pnpm-lock.yaml

Lines changed: 206 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- Add nanoid
2+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
3+
4+
CREATE OR REPLACE FUNCTION nanoid(size int DEFAULT 14)
5+
RETURNS text AS $$
6+
DECLARE
7+
id text := '';
8+
i int := 0;
9+
urlAlphabet char(64) := 'ModuleSymbhasOwnPr-0123456789ABCDEFGHNRVfgctiUvz_KqYTJkLxpZXIjQW';
10+
bytes bytea := gen_random_bytes(size);
11+
byte int;
12+
pos int;
13+
BEGIN
14+
WHILE i < size LOOP
15+
byte := get_byte(bytes, i);
16+
pos := (byte & 63) + 1; -- + 1 because substr starts at 1 for some reason
17+
id := id || substr(urlAlphabet, pos, 1);
18+
i = i + 1;
19+
END LOOP;
20+
RETURN id;
21+
END
22+
$$ LANGUAGE PLPGSQL STABLE;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- CreateTable
2+
CREATE TABLE "scorecard" (
3+
"id" VARCHAR(14) NOT NULL DEFAULT nanoid(),
4+
5+
CONSTRAINT "scorecard_pkey" PRIMARY KEY ("id")
6+
);

prisma/migrations/migration_lock.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Please do not edit this file manually
2+
# It should be added in your version-control system (e.g., Git)
3+
provider = "postgresql"

prisma/schema.prisma

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This is your Prisma schema file,
2+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
3+
4+
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5+
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6+
7+
generator client {
8+
provider = "prisma-client-js"
9+
}
10+
11+
datasource db {
12+
provider = "postgresql"
13+
url = env("DATABASE_URL")
14+
}
15+
16+
model scorecard {
17+
id String @id @default(dbgenerated("nanoid()")) @db.VarChar(14)
18+
19+
// TODO add the rest of the fields
20+
}

src/api/api.module.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
import { GlobalProvidersModule } from 'src/shared/modules/global/globalProviders.module';
3+
import { HealthCheckController } from './health-check/healthCheck.controller';
4+
5+
@Module({
6+
imports: [GlobalProvidersModule],
7+
controllers: [HealthCheckController],
8+
providers: [],
9+
})
10+
export class ApiModule {}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Controller, Get } from '@nestjs/common';
2+
import { ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
3+
import { PrismaService } from 'src/shared/modules/global/prisma.service';
4+
5+
export enum HealthCheckStatus {
6+
healthy = 'healthy',
7+
unhealthy = 'unhealthy',
8+
}
9+
10+
export class GetHealthCheckResponseDto {
11+
@ApiProperty({
12+
description: 'The status of the health check',
13+
enum: HealthCheckStatus,
14+
example: HealthCheckStatus.healthy,
15+
})
16+
status: HealthCheckStatus;
17+
18+
@ApiProperty({
19+
description: 'Database connection status',
20+
example: 'Connected',
21+
})
22+
database: string;
23+
}
24+
25+
@ApiTags('Healthcheck')
26+
@Controller('/api')
27+
export class HealthCheckController {
28+
constructor(private readonly prisma: PrismaService) {}
29+
30+
@Get('/healthcheck')
31+
@ApiOperation({ summary: 'Execute a health check' })
32+
healthCheck(): GetHealthCheckResponseDto {
33+
const response = new GetHealthCheckResponseDto();
34+
35+
try {
36+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
37+
this.prisma.scorecard.findFirst({
38+
select: {
39+
id: true,
40+
},
41+
});
42+
43+
response.status = HealthCheckStatus.healthy;
44+
response.database = 'connected';
45+
} catch (error) {
46+
console.error('Health check failed', error);
47+
response.status = HealthCheckStatus.unhealthy;
48+
}
49+
50+
return response;
51+
}
52+
}

src/app.controller.spec.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/app.controller.ts

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/app.module.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Module } from '@nestjs/common';
2-
import { AppController } from './app.controller';
3-
import { AppService } from './app.service';
2+
import { ApiModule } from './api/api.module';
43

54
@Module({
6-
imports: [],
7-
controllers: [AppController],
8-
providers: [AppService],
5+
imports: [ApiModule],
6+
controllers: [],
7+
providers: [],
98
})
109
export class AppModule {}

src/app.service.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/main.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,70 @@
11
import { NestFactory } from '@nestjs/core';
22
import { AppModule } from './app.module';
3+
import { ValidationPipe } from '@nestjs/common';
4+
import * as cors from 'cors';
5+
import { NestExpressApplication } from '@nestjs/platform-express';
6+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
7+
import { ApiModule } from './api/api.module';
38

49
async function bootstrap() {
5-
const app = await NestFactory.create(AppModule);
10+
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
11+
rawBody: true,
12+
});
13+
14+
// CORS related settings
15+
const corsConfig: cors.CorsOptions = {
16+
allowedHeaders:
17+
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Access-Control-Allow-Origin, Access-Control-Allow-Headers,currentOrg,overrideOrg,x-atlassian-cloud-id,x-api-key,x-orgid',
18+
credentials: true,
19+
origin: process.env.CORS_ALLOWED_ORIGIN
20+
? new RegExp(process.env.CORS_ALLOWED_ORIGIN)
21+
: ['http://localhost:3000', /\.localhost:3000$/],
22+
methods: 'GET, POST, OPTIONS, PUT, DELETE, PATCH',
23+
};
24+
app.use(cors(corsConfig));
25+
26+
// Add body parsers
27+
app.useBodyParser('json', { limit: '15mb' });
28+
app.useBodyParser('urlencoded', { limit: '15mb', extended: true });
29+
// Add the global validation pipe to auto-map and validate DTOs
30+
// Note that the whitelist option sanitizes input DTOs so only properties defined on the class are set
31+
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
32+
33+
// Setup swagger
34+
// TODO: finish this and make it so this block only runs in non-prod
35+
const config = new DocumentBuilder()
36+
.setTitle('API')
37+
.setDescription('TC Review API documentation')
38+
.setVersion('1.0')
39+
.addTag('TC Review')
40+
.addBearerAuth({
41+
type: 'http',
42+
scheme: 'bearer',
43+
bearerFormat: 'JWT',
44+
name: 'JWT',
45+
description: 'Enter JWT access token',
46+
in: 'header',
47+
})
48+
.build();
49+
const document = SwaggerModule.createDocument(app, config, {
50+
include: [ApiModule],
51+
});
52+
SwaggerModule.setup('api-docs', app, document);
53+
54+
// Add an event handler to log uncaught promise rejections and prevent the server from crashing
55+
process.on('unhandledRejection', (reason, promise) => {
56+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
57+
});
58+
59+
// Add an event handler to log uncaught errors and prevent the server from crashing
60+
process.on('uncaughtException', (error: Error) => {
61+
console.error(
62+
`Unhandled Error at: ${error}\n` + `Exception origin: ${error.stack}`,
63+
);
64+
});
65+
66+
// Listen on port
667
await app.listen(process.env.PORT ?? 3000);
768
}
69+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
870
bootstrap();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Global, Module } from '@nestjs/common';
2+
import { PrismaService } from './prisma.service';
3+
4+
// Global module for providing global providers
5+
// Add any provider you want to be global here
6+
@Global()
7+
@Module({
8+
providers: [PrismaService],
9+
exports: [PrismaService],
10+
})
11+
export class GlobalProvidersModule {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Injectable, OnModuleInit } from '@nestjs/common';
2+
import { Prisma, PrismaClient } from '@prisma/client';
3+
4+
@Injectable()
5+
export class PrismaService
6+
extends PrismaClient<
7+
Prisma.PrismaClientOptions,
8+
'query' | 'info' | 'warn' | 'error'
9+
>
10+
implements OnModuleInit
11+
{
12+
constructor() {
13+
super({
14+
log: [
15+
{ level: 'query', emit: 'event' },
16+
{ level: 'info', emit: 'event' },
17+
{ level: 'warn', emit: 'event' },
18+
{ level: 'error', emit: 'event' },
19+
],
20+
});
21+
}
22+
23+
async onModuleInit() {
24+
await this.$connect();
25+
}
26+
}

0 commit comments

Comments
 (0)