commit
5d83aaf11b
22 changed files with 7896 additions and 0 deletions
-
24.eslintrc.js
-
37.gitignore
-
4.prettierrc
-
62README.md
-
6compodoc.server.json
-
9dev/pg.yml
-
6example.env
-
4nest-cli.json
-
79package.json
-
22src/app.controller.spec.ts
-
12src/app.controller.ts
-
28src/app.module.ts
-
8src/app.service.ts
-
9src/config/config.module.ts
-
102src/config/config.service.spec.ts
-
61src/config/config.service.ts
-
8src/main.ts
-
28test/app.e2e-spec.ts
-
9test/jest-e2e.json
-
4tsconfig.build.json
-
16tsconfig.json
-
7358yarn.lock
@ -0,0 +1,24 @@ |
|||||
|
module.exports = { |
||||
|
parser: '@typescript-eslint/parser', |
||||
|
parserOptions: { |
||||
|
project: 'tsconfig.json', |
||||
|
sourceType: 'module', |
||||
|
}, |
||||
|
plugins: ['@typescript-eslint/eslint-plugin'], |
||||
|
extends: [ |
||||
|
'plugin:@typescript-eslint/recommended', |
||||
|
'plugin:prettier/recommended', |
||||
|
], |
||||
|
root: true, |
||||
|
env: { |
||||
|
node: true, |
||||
|
jest: true, |
||||
|
}, |
||||
|
ignorePatterns: ['.eslintrc.js'], |
||||
|
rules: { |
||||
|
'@typescript-eslint/interface-name-prefix': 'off', |
||||
|
'@typescript-eslint/explicit-function-return-type': 'off', |
||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off', |
||||
|
'@typescript-eslint/no-explicit-any': 'off', |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,37 @@ |
|||||
|
# compiled output |
||||
|
/dist |
||||
|
/node_modules |
||||
|
|
||||
|
# Logs |
||||
|
logs |
||||
|
*.log |
||||
|
npm-debug.log* |
||||
|
pnpm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
lerna-debug.log* |
||||
|
|
||||
|
# OS |
||||
|
.DS_Store |
||||
|
|
||||
|
# Tests |
||||
|
/coverage |
||||
|
/.nyc_output |
||||
|
|
||||
|
# IDEs and editors |
||||
|
/.idea |
||||
|
.project |
||||
|
.classpath |
||||
|
.c9/ |
||||
|
*.launch |
||||
|
.settings/ |
||||
|
*.sublime-workspace |
||||
|
|
||||
|
# IDE - VSCode |
||||
|
.vscode/* |
||||
|
!.vscode/settings.json |
||||
|
!.vscode/tasks.json |
||||
|
!.vscode/launch.json |
||||
|
!.vscode/extensions.json |
||||
|
.env |
||||
|
docs/ |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"singleQuote": true, |
||||
|
"trailingComma": "all" |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
<h1 align="center">Welcome to TP on CI and CircleCI 👋</h1> |
||||
|
|
||||
|
|
||||
|
You can find the codelabs here on iCampus. |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
```bash |
||||
|
$ yarn install |
||||
|
``` |
||||
|
|
||||
|
## Running the app |
||||
|
|
||||
|
```bash |
||||
|
# development |
||||
|
yarn start |
||||
|
|
||||
|
# watch mode |
||||
|
yarn start:dev |
||||
|
|
||||
|
# production mode |
||||
|
$ yarn start:prod |
||||
|
``` |
||||
|
|
||||
|
## Test |
||||
|
|
||||
|
```bash |
||||
|
# unit tests |
||||
|
$ yarn test |
||||
|
|
||||
|
# e2e tests |
||||
|
$ yarn test:e2e |
||||
|
``` |
||||
|
|
||||
|
## Lints |
||||
|
|
||||
|
```bash |
||||
|
# Check |
||||
|
$ yarn format:check |
||||
|
|
||||
|
# Format code |
||||
|
$ yarn format:check |
||||
|
|
||||
|
$ yarn lint |
||||
|
``` |
||||
|
|
||||
|
## Documentation |
||||
|
|
||||
|
```bash |
||||
|
# Run it locally |
||||
|
$ yarn doc |
||||
|
|
||||
|
# Build it |
||||
|
$ yarn doc:build |
||||
|
``` |
||||
|
|
||||
|
## Author |
||||
|
|
||||
|
👤 **Nicolas Beaussart <nic.beaussart@gmail.com>** |
||||
|
|
||||
|
- Twitter: [@beaussan](https://twitter.com/beaussan) |
||||
|
- Github: [@beaussan](https://github.com/beaussan) |
||||
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"port": 9911, |
||||
|
"name": "TP_CI SERVER", |
||||
|
"output": "docs", |
||||
|
"tsconfig": "./tsconfig.json" |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
version: '2' |
||||
|
services: |
||||
|
ognion-boilerplate-postgresql: |
||||
|
image: postgres:9.6.5 |
||||
|
environment: |
||||
|
- POSTGRES_USER=tpCi |
||||
|
- POSTGRES_PASSWORD=someNotSecurePassword |
||||
|
ports: |
||||
|
- 5432:5432 |
||||
@ -0,0 +1,6 @@ |
|||||
|
## LOGGER |
||||
|
LOG_LEVEL=debug |
||||
|
LOG_SQL_REQUEST=false |
||||
|
|
||||
|
## DB [TypeORM] |
||||
|
DATABASE_URL=postgres://tpCi:someNotSecurePassword@localhost:5432/tpCi |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"collection": "@nestjs/schematics", |
||||
|
"sourceRoot": "src" |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
{ |
||||
|
"name": "2021-2022-devops-03-ci-tp", |
||||
|
"version": "0.0.1", |
||||
|
"description": "", |
||||
|
"author": "", |
||||
|
"private": true, |
||||
|
"license": "UNLICENSED", |
||||
|
"scripts": { |
||||
|
"prebuild": "rimraf dist", |
||||
|
"build": "nest build", |
||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", |
||||
|
"format:check": "prettier --list-different \"src/**/*.ts\" \"test/**/*.ts\"", |
||||
|
"start": "nest start", |
||||
|
"start:dev": "nest start --watch", |
||||
|
"start:debug": "nest start --debug --watch", |
||||
|
"start:prod": "node dist/main", |
||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", |
||||
|
"doc:build": "compodoc -c compodoc.server.json", |
||||
|
"doc": "compodoc -c compodoc.server.json -s -o -w", |
||||
|
"test": "jest", |
||||
|
"test:watch": "jest --watch", |
||||
|
"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" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@nestjs/common": "^8.0.0", |
||||
|
"@nestjs/core": "^8.0.0", |
||||
|
"@nestjs/platform-express": "^8.0.0", |
||||
|
"@nestjs/typeorm": "8.0.2", |
||||
|
"dotenv": "10.0.0", |
||||
|
"pg": "8.7.1", |
||||
|
"reflect-metadata": "^0.1.13", |
||||
|
"rimraf": "^3.0.2", |
||||
|
"rxjs": "^7.2.0", |
||||
|
"typeorm": "0.2.38", |
||||
|
"zod": "3.9.8" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@compodoc/compodoc": "1.1.15", |
||||
|
"@nestjs/cli": "^8.0.0", |
||||
|
"@nestjs/schematics": "^8.0.0", |
||||
|
"@nestjs/testing": "^8.0.0", |
||||
|
"@types/express": "^4.17.13", |
||||
|
"@types/jest": "^26.0.24", |
||||
|
"@types/node": "^16.0.0", |
||||
|
"@types/supertest": "^2.0.11", |
||||
|
"@typescript-eslint/eslint-plugin": "^4.28.2", |
||||
|
"@typescript-eslint/parser": "^4.28.2", |
||||
|
"eslint": "^7.30.0", |
||||
|
"eslint-config-prettier": "^8.3.0", |
||||
|
"eslint-plugin-prettier": "^3.4.0", |
||||
|
"jest": "27.0.6", |
||||
|
"prettier": "^2.3.2", |
||||
|
"supertest": "^6.1.3", |
||||
|
"ts-jest": "^27.0.3", |
||||
|
"ts-loader": "^9.2.3", |
||||
|
"ts-node": "^10.0.0", |
||||
|
"tsconfig-paths": "^3.10.1", |
||||
|
"typescript": "^4.3.5" |
||||
|
}, |
||||
|
"jest": { |
||||
|
"moduleFileExtensions": [ |
||||
|
"js", |
||||
|
"json", |
||||
|
"ts" |
||||
|
], |
||||
|
"rootDir": "src", |
||||
|
"testRegex": ".*\\.spec\\.ts$", |
||||
|
"transform": { |
||||
|
"^.+\\.(t|j)s$": "ts-jest" |
||||
|
}, |
||||
|
"collectCoverageFrom": [ |
||||
|
"**/*.(t|j)s" |
||||
|
], |
||||
|
"coverageDirectory": "../coverage", |
||||
|
"testEnvironment": "node" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
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!'); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { Controller, Get } from '@nestjs/common'; |
||||
|
import { AppService } from './app.service'; |
||||
|
|
||||
|
@Controller() |
||||
|
export class AppController { |
||||
|
constructor(private readonly appService: AppService) {} |
||||
|
|
||||
|
@Get() |
||||
|
getHello(): string { |
||||
|
return this.appService.getHello(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
import { Module } from '@nestjs/common'; |
||||
|
import { AppController } from './app.controller'; |
||||
|
import { AppService } from './app.service'; |
||||
|
import { ConfigModule } from './config/config.module'; |
||||
|
import { ConfigService } from './config/config.service'; |
||||
|
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'; |
||||
|
|
||||
|
@Module({ |
||||
|
imports: [ |
||||
|
ConfigModule, |
||||
|
TypeOrmModule.forRootAsync({ |
||||
|
imports: [ConfigModule], |
||||
|
useFactory: async ( |
||||
|
configService: ConfigService, |
||||
|
): Promise<TypeOrmModuleOptions> => ({ |
||||
|
type: 'postgres', |
||||
|
url: configService.databaseUrl, |
||||
|
entities: [__dirname + '/**/*.entity{.ts,.js}'], |
||||
|
synchronize: true, |
||||
|
logging: configService.isLoggingDb ? 'all' : false, |
||||
|
}), |
||||
|
inject: [ConfigService], |
||||
|
}), |
||||
|
], |
||||
|
controllers: [AppController], |
||||
|
providers: [AppService], |
||||
|
}) |
||||
|
export class AppModule {} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import { Injectable } from '@nestjs/common'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class AppService { |
||||
|
getHello(): string { |
||||
|
return 'Hello World!'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import { Global, Module } from '@nestjs/common'; |
||||
|
import { ConfigService } from './config.service'; |
||||
|
|
||||
|
@Global() |
||||
|
@Module({ |
||||
|
providers: [ConfigService], |
||||
|
exports: [ConfigService], |
||||
|
}) |
||||
|
export class ConfigModule {} |
||||
@ -0,0 +1,102 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { ConfigService } from './config.service'; |
||||
|
import { expand } from 'rxjs/operators'; |
||||
|
|
||||
|
describe('ConfigService', () => { |
||||
|
const OLD_ENV = process.env; |
||||
|
|
||||
|
beforeEach(() => { |
||||
|
jest.resetModules(); // this is important - it clears the cache
|
||||
|
process.env = { ...OLD_ENV }; |
||||
|
delete process.env.NODE_ENV; |
||||
|
}); |
||||
|
|
||||
|
afterEach(() => { |
||||
|
process.env = OLD_ENV; |
||||
|
}); |
||||
|
|
||||
|
const loadService = async (): Promise<ConfigService> => { |
||||
|
const module: TestingModule = await Test.createTestingModule({ |
||||
|
providers: [ConfigService], |
||||
|
}).compile(); |
||||
|
|
||||
|
return module.get<ConfigService>(ConfigService); |
||||
|
}; |
||||
|
|
||||
|
it('should have default values', async () => { |
||||
|
const dbUrl = 'gotodb please'; |
||||
|
const jwtSecrets = 'shhhh, secret'; |
||||
|
|
||||
|
// set the variables
|
||||
|
process.env.NODE_ENV = undefined; |
||||
|
process.env.API_PORT = undefined; |
||||
|
process.env.API_PROTOCOL = undefined; |
||||
|
process.env.LOG_LEVEL = undefined; |
||||
|
process.env.LOG_SQL_REQUEST = undefined; |
||||
|
process.env.DATABASE_URL = dbUrl; |
||||
|
process.env.JWT_SECRET = jwtSecrets; |
||||
|
|
||||
|
const configService = await loadService(); |
||||
|
|
||||
|
expect(configService.nodeEnv).toEqual('development'); |
||||
|
expect(configService.loggerLevel).toEqual('debug'); |
||||
|
expect(configService.databaseUrl).toEqual(dbUrl); |
||||
|
expect(configService.isLoggingDb).toEqual(false); |
||||
|
}); |
||||
|
|
||||
|
it('should error when no db url set', async () => { |
||||
|
const jwtSecrets = 'shhhh, secret'; |
||||
|
|
||||
|
// set the variables
|
||||
|
process.env.NODE_ENV = undefined; |
||||
|
process.env.API_PORT = undefined; |
||||
|
process.env.API_PROTOCOL = undefined; |
||||
|
process.env.LOG_LEVEL = undefined; |
||||
|
process.env.LOG_SQL_REQUEST = undefined; |
||||
|
process.env.DATABASE_URL = undefined; |
||||
|
process.env.JWT_SECRET = jwtSecrets; |
||||
|
|
||||
|
try { |
||||
|
const configService = await loadService(); |
||||
|
fail(); |
||||
|
} catch (e) { |
||||
|
expect(e.message).toMatchInlineSnapshot(`
|
||||
|
"Config validation error: [ |
||||
|
{ |
||||
|
\\"code\\": \\"invalid_type\\", |
||||
|
\\"expected\\": \\"string\\", |
||||
|
\\"received\\": \\"undefined\\", |
||||
|
\\"path\\": [ |
||||
|
\\"DATABASE_URL\\" |
||||
|
], |
||||
|
\\"message\\": \\"Required\\" |
||||
|
} |
||||
|
]" |
||||
|
`);
|
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
it('should set correct values', async () => { |
||||
|
const dbUrl = 'gotodb please'; |
||||
|
const jwtSecrets = 'shhhh, secret'; |
||||
|
const nodeEnv = 'test'; |
||||
|
const apiPort = 2020; |
||||
|
const logLevel = 'silly'; |
||||
|
const logSqlRequest = true; |
||||
|
|
||||
|
// set the variables
|
||||
|
process.env.NODE_ENV = nodeEnv; |
||||
|
process.env.API_PORT = `${apiPort}`; |
||||
|
process.env.LOG_LEVEL = logLevel; |
||||
|
process.env.LOG_SQL_REQUEST = `${logSqlRequest}`; |
||||
|
process.env.DATABASE_URL = dbUrl; |
||||
|
process.env.JWT_SECRET = jwtSecrets; |
||||
|
|
||||
|
const configService = await loadService(); |
||||
|
|
||||
|
expect(configService.nodeEnv).toEqual(nodeEnv); |
||||
|
expect(configService.loggerLevel).toEqual(logLevel); |
||||
|
expect(configService.databaseUrl).toEqual(dbUrl); |
||||
|
expect(configService.isLoggingDb).toEqual(logSqlRequest); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,61 @@ |
|||||
|
import { Injectable } from '@nestjs/common'; |
||||
|
import * as z from 'zod'; |
||||
|
import { config as parseConfig } from 'dotenv'; |
||||
|
|
||||
|
const schema = z.object({ |
||||
|
NODE_ENV: z |
||||
|
.enum(['development', 'production', 'test']) |
||||
|
.default('development'), |
||||
|
LOG_LEVEL: z |
||||
|
.enum(['error', 'warning', 'info', 'debug', 'silly']) |
||||
|
.default('debug'), |
||||
|
DATABASE_URL: z.string().nonempty(), |
||||
|
LOG_SQL_REQUEST: z |
||||
|
.string() |
||||
|
.transform((value) => value === 'true') |
||||
|
.default('false'), |
||||
|
}); |
||||
|
|
||||
|
type EnvConfig = z.infer<typeof schema>; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class ConfigService { |
||||
|
private readonly envConfig: EnvConfig; |
||||
|
|
||||
|
constructor() { |
||||
|
try { |
||||
|
parseConfig(); |
||||
|
} catch (e) {} |
||||
|
|
||||
|
this.envConfig = this.validateInput(process.env); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Ensures all needed variables are set, and returns the validated JavaScript object |
||||
|
* including the applied default values. |
||||
|
*/ |
||||
|
private validateInput(envConfig: any): EnvConfig { |
||||
|
const result = schema.safeParse(envConfig); |
||||
|
|
||||
|
if (result.success === false) { |
||||
|
throw new Error(`Config validation error: ${result.error.toString()}`); |
||||
|
} |
||||
|
return result.data; |
||||
|
} |
||||
|
|
||||
|
get nodeEnv(): string { |
||||
|
return this.envConfig.NODE_ENV; |
||||
|
} |
||||
|
|
||||
|
get databaseUrl(): string { |
||||
|
return this.envConfig.DATABASE_URL; |
||||
|
} |
||||
|
|
||||
|
get isLoggingDb(): boolean { |
||||
|
return this.envConfig.LOG_SQL_REQUEST; |
||||
|
} |
||||
|
|
||||
|
get loggerLevel(): string { |
||||
|
return this.envConfig.LOG_LEVEL; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import { NestFactory } from '@nestjs/core'; |
||||
|
import { AppModule } from './app.module'; |
||||
|
|
||||
|
async function bootstrap() { |
||||
|
const app = await NestFactory.create(AppModule); |
||||
|
await app.listen(3000); |
||||
|
} |
||||
|
bootstrap(); |
||||
@ -0,0 +1,28 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { INestApplication } from '@nestjs/common'; |
||||
|
import * as request from 'supertest'; |
||||
|
import { AppModule } from './../src/app.module'; |
||||
|
|
||||
|
describe('AppController (e2e)', () => { |
||||
|
let app: INestApplication; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({ |
||||
|
imports: [AppModule], |
||||
|
}).compile(); |
||||
|
|
||||
|
app = moduleFixture.createNestApplication(); |
||||
|
await app.init(); |
||||
|
}); |
||||
|
|
||||
|
afterEach(async () => { |
||||
|
await app.close(); |
||||
|
}); |
||||
|
|
||||
|
it('/ (GET)', () => { |
||||
|
return request(app.getHttpServer()) |
||||
|
.get('/') |
||||
|
.expect(200) |
||||
|
.expect('Hello World!'); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,9 @@ |
|||||
|
{ |
||||
|
"moduleFileExtensions": ["js", "json", "ts"], |
||||
|
"rootDir": ".", |
||||
|
"testEnvironment": "node", |
||||
|
"testRegex": ".e2e-spec.ts$", |
||||
|
"transform": { |
||||
|
"^.+\\.(t|j)s$": "ts-jest" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"extends": "./tsconfig.json", |
||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"module": "commonjs", |
||||
|
"declaration": true, |
||||
|
"removeComments": true, |
||||
|
"emitDecoratorMetadata": true, |
||||
|
"experimentalDecorators": true, |
||||
|
"allowSyntheticDefaultImports": true, |
||||
|
"target": "es2017", |
||||
|
"sourceMap": true, |
||||
|
"outDir": "./dist", |
||||
|
"baseUrl": "./", |
||||
|
"incremental": true, |
||||
|
"skipLibCheck": true |
||||
|
} |
||||
|
} |
||||
7358
yarn.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue