improve .env parsing
This commit is contained in:
1
apps/backend/.gitignore
vendored
1
apps/backend/.gitignore
vendored
@@ -54,3 +54,4 @@ pids
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"migrate:latest": "ts-node src/migrate.ts"
|
||||
"migrate:latest": "ts-node src/migrate.ts",
|
||||
"kysely:codegen": "kysely-codegen --out-file ./src/database/db.d.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@my-monorepo/common": "workspace:*",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"dotenv": "^17.2.1",
|
||||
"kysely": "^0.28.3",
|
||||
"pg": "^8.16.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
@@ -49,6 +51,7 @@
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"kysely-codegen": "^0.18.5",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const app: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('hello')
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { AuthController } from './controllers/auth/auth.controller';
|
||||
import { AuthController } from './app/auth/auth.controller';
|
||||
import { DatabaseService } from './database/database.service';
|
||||
import { AuthService } from './app/auth/auth.service';
|
||||
import { UserRegistryService } from './app/users/user-registry.service';
|
||||
import { EnvService } from './env/env.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AppController, AuthController],
|
||||
providers: [AppService],
|
||||
controllers: [AuthController],
|
||||
providers: [DatabaseService, AuthService, UserRegistryService, EnvService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
14
apps/backend/src/app/auth/auth.controller.ts
Normal file
14
apps/backend/src/app/auth/auth.controller.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Controller, Post } from '@nestjs/common';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
@Post('credentials')
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async postCredentials() {
|
||||
return this.authService.verifyCredentials();
|
||||
}
|
||||
}
|
||||
18
apps/backend/src/app/auth/auth.service.spec.ts
Normal file
18
apps/backend/src/app/auth/auth.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
17
apps/backend/src/app/auth/auth.service.ts
Normal file
17
apps/backend/src/app/auth/auth.service.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { UserRegistryService } from '../users/user-registry.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(private userRegistry: UserRegistryService) {}
|
||||
|
||||
async verifyCredentials() {
|
||||
const isValidCredential = await this.userRegistry.existsEmailPassword(
|
||||
'',
|
||||
'',
|
||||
);
|
||||
if (!isValidCredential) {
|
||||
throw new UnauthorizedException('invalid credentials');
|
||||
}
|
||||
}
|
||||
}
|
||||
18
apps/backend/src/app/users/user-registry.service.spec.ts
Normal file
18
apps/backend/src/app/users/user-registry.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UserRegistryService } from './user-registry.service';
|
||||
|
||||
describe('UserRegistryService', () => {
|
||||
let service: UserRegistryService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [UserRegistryService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<UserRegistryService>(UserRegistryService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
18
apps/backend/src/app/users/user-registry.service.ts
Normal file
18
apps/backend/src/app/users/user-registry.service.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DatabaseService } from '../../database/database.service';
|
||||
import { isDefined } from '@my-monorepo/common';
|
||||
|
||||
@Injectable()
|
||||
export class UserRegistryService {
|
||||
constructor(private databaseService: DatabaseService) {}
|
||||
|
||||
async existsEmailPassword(email: string, hashedPassword: string) {
|
||||
const id = await this.databaseService.database
|
||||
.selectFrom('users')
|
||||
.select('id')
|
||||
.where((eb) => eb('email', '=', email).and('hash', '=', hashedPassword))
|
||||
.executeTakeFirst();
|
||||
|
||||
return isDefined(id);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { AuthApiService } from '@my-monorepo/common';
|
||||
import { Controller, Post } from '@nestjs/common';
|
||||
|
||||
@Controller(AuthApiService.baseUrl)
|
||||
export class AuthController implements AuthApiService {
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
@Post(AuthApiService.postCredentials)
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async postCredentials() {
|
||||
return 'hello';
|
||||
}
|
||||
}
|
||||
18
apps/backend/src/database/database.service.spec.ts
Normal file
18
apps/backend/src/database/database.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { DatabaseService } from './database.service';
|
||||
|
||||
describe('DatabaseService', () => {
|
||||
let service: DatabaseService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [DatabaseService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<DatabaseService>(DatabaseService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
33
apps/backend/src/database/database.service.ts
Normal file
33
apps/backend/src/database/database.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Pool } from 'pg';
|
||||
import { Kysely, PostgresDialect } from 'kysely';
|
||||
import { DB } from './db';
|
||||
import { EnvService } from '../env/env.service';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseService {
|
||||
database: Kysely<DB>;
|
||||
|
||||
constructor(envService: EnvService) {
|
||||
const config = envService.config;
|
||||
const dialect = new PostgresDialect({
|
||||
pool: new Pool({
|
||||
database: config.DATABASE,
|
||||
host: config.DATABASE_HOST,
|
||||
user: config.DATABASE_USER,
|
||||
password: config.DATABASE_PASSWORD,
|
||||
port: config.DATABASE_PORT,
|
||||
max: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
// Database interface is passed to Kysely's constructor, and from now on, Kysely
|
||||
// knows your database structure.
|
||||
// Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
|
||||
// to communicate with your database.
|
||||
this.database = new Kysely<DB>({
|
||||
dialect,
|
||||
});
|
||||
}
|
||||
}
|
||||
29
apps/backend/src/database/db.d.ts
vendored
Normal file
29
apps/backend/src/database/db.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* This file was generated by kysely-codegen.
|
||||
* Please do not edit it manually.
|
||||
*/
|
||||
|
||||
import type { ColumnType } from "kysely";
|
||||
|
||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S, I | undefined, U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
|
||||
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
|
||||
|
||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
|
||||
export interface Users {
|
||||
created_at: Generated<Timestamp>;
|
||||
deleted_at: Timestamp | null;
|
||||
email: string;
|
||||
first_name: string;
|
||||
hash: string | null;
|
||||
id: Generated<Int8>;
|
||||
last_name: string;
|
||||
updated_at: Generated<Timestamp>;
|
||||
}
|
||||
|
||||
export interface DB {
|
||||
users: Users;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { Pool } from 'pg';
|
||||
import { Kysely, PostgresDialect } from 'kysely';
|
||||
|
||||
const dialect = new PostgresDialect({
|
||||
// TODO : temporary data, make to .env
|
||||
pool: new Pool({
|
||||
database: 'cowsi',
|
||||
host: 'localhost',
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
port: 5432,
|
||||
max: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
// Database interface is passed to Kysely's constructor, and from now on, Kysely
|
||||
// knows your database structure.
|
||||
// Dialect is passed to Kysely's constructor, and from now on, Kysely knows how
|
||||
// to communicate with your database.
|
||||
export const db = new Kysely<any>({
|
||||
dialect,
|
||||
});
|
||||
18
apps/backend/src/env/env.service.spec.ts
vendored
Normal file
18
apps/backend/src/env/env.service.spec.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { EnvService } from './env.service';
|
||||
|
||||
describe('EnvService', () => {
|
||||
let service: EnvService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [EnvService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<EnvService>(EnvService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
27
apps/backend/src/env/env.service.ts
vendored
Normal file
27
apps/backend/src/env/env.service.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import z from 'zod';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
const envParser = z.object({
|
||||
DATABASE: z.string(),
|
||||
DATABASE_USER: z.string(),
|
||||
DATABASE_PASSWORD: z.string(),
|
||||
DATABASE_HOST: z.string(),
|
||||
DATABASE_PORT: z.coerce.number(),
|
||||
});
|
||||
|
||||
export type EnvConfig = Readonly<z.infer<typeof envParser>>;
|
||||
|
||||
@Injectable()
|
||||
export class EnvService {
|
||||
readonly config: EnvConfig;
|
||||
|
||||
constructor() {
|
||||
console.log('paul-debug', dotenv);
|
||||
const res = dotenv.config();
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
this.config = envParser.parse(res.parsed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user