Browse Source

Initial commit

main
HenriBoulnois 5 years ago
commit
cfa9c9b224
  1. 161
      .circleci/config.yml
  2. 24
      .eslintrc.js
  3. 37
      .gitignore
  4. 4
      .prettierrc
  5. 10
      Dockerfile
  6. 62
      README.md
  7. 6
      compodoc.server.json
  8. 9
      dev/pg.yml
  9. 7
      example.env
  10. 4
      nest-cli.json
  11. 79
      package.json
  12. 22
      src/app.controller.spec.ts
  13. 12
      src/app.controller.ts
  14. 28
      src/app.module.ts
  15. 8
      src/app.service.ts
  16. 9
      src/config/config.module.ts
  17. 102
      src/config/config.service.spec.ts
  18. 68
      src/config/config.service.ts
  19. 12
      src/main.ts
  20. 28
      test/app.e2e-spec.ts
  21. 9
      test/jest-e2e.json
  22. 4
      tsconfig.build.json
  23. 16
      tsconfig.json
  24. 7358
      yarn.lock

161
.circleci/config.yml

@ -0,0 +1,161 @@
version: 2
jobs:
build: &shared-config
docker:
- image: circleci/node:lts-fermium
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- dependencies-{{ checksum "package.json" }}-v1
# fallback to using the latest cache if no exact match is found
- dependencies-
- run: yarn install
- save_cache:
paths:
- node_modules
key: dependencies-{{ checksum "package.json" }}-v1
lint:
<<: *shared-config
steps:
- checkout
- restore_cache:
key: dependencies-{{ checksum "package.json" }}-v1
- run: yarn lint
- run: yarn format:check
server-doc-build:
<<: *shared-config
steps:
- checkout
- restore_cache:
key: dependencies-{{ checksum "package.json" }}-v1
- run: yarn doc:build
- run:
name: Copy deployment artifacts to workspace
command: |
cp docs/ /tmp/server-doc -r
- store_artifacts:
path: /tmp/server-doc
- persist_to_workspace:
root: /tmp
paths:
- server-doc
server-doc-deploy:
<<: *shared-config
working_directory: ~/deploy-doc-server
steps:
- attach_workspace:
at: /tmp
- run:
name: Deploy app
command: echo todo && false
back-test-unit:
<<: *shared-config
steps:
- checkout
- restore_cache:
key: dependencies-{{ checksum "package.json" }}-v1
- run:
command: yarn test
environment:
DATABASE_URL: postgres://psqluer:psqlpassword@localhost:5432/psqluer
back-test-e2e:
docker:
- image: circleci/node:lts-fermium
- image: circleci/postgres:9.6.5
environment:
POSTGRES_DB: psqluer
POSTGRES_USER: psqluer
POSTGRES_PASSWORD: psqlpassword
working_directory: ~/repo
steps:
- checkout
- restore_cache:
key: dependencies-{{ checksum "package.json" }}-v1
- run:
command: yarn test:e2e
environment:
DATABASE_URL: postgres://psqluer:psqlpassword@localhost:5432/psqluer
docker-build-and-push:
working_directory: /dockerapp
docker:
- image: docker:17.05.0-ce-git
steps:
- checkout
- setup_remote_docker
- run:
name: Build application Docker image
command: |
# docker build --cache-from=app -t app .
echo true
- deploy:
name: Publish application to docker hub
command: |
# docker login -e $DOCKER_HUB_EMAIL -u $DOCKER_HUB_USER_ID -p $DOCKER_HUB_PWD
# docker tag app $DOCKER_HUB_USER_ID/my-awesome-ci-expr:$CIRCLE_BUILD_NUM
# docker tag app $DOCKER_HUB_USER_ID/my-awesome-ci-expr:latest
# docker push $DOCKER_HUB_USER_ID/my-awesome-ci-expr:$CIRCLE_BUILD_NUM
# docker push $DOCKER_HUB_USER_ID/my-awesome-ci-expr:latest
echo true
back-deploy-heroku:
docker:
- image: buildpack-deps:trusty
steps:
- checkout
- run:
name: Heroku Deploy
command: echo todo && false
- run:
name: Smoke Test
command: echo todo && false
workflows:
version: 2
build-test-and-lint:
jobs:
- build
- back-test-unit:
requires:
- build
- back-test-e2e:
requires:
- build
- lint:
requires:
- build
- docker-build-and-push:
requires:
- build
- lint
- back-test-e2e
- back-test-unit
filters:
branches:
only: main
- back-deploy-heroku:
requires:
- build
- lint
- back-test-e2e
- back-test-unit
filters:
branches:
only: main
- server-doc-build:
requires:
- build
- lint
- back-test-e2e
- back-test-unit
filters:
branches:
only: main
- server-doc-deploy:
requires:
- server-doc-build
filters:
branches:
only: main

24
.eslintrc.js

@ -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',
},
};

37
.gitignore

@ -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/

4
.prettierrc

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

10
Dockerfile

@ -0,0 +1,10 @@
FROM node:lts-fermium
WORKDIR /nest-server
COPY . .
RUN yarn install
CMD ["yarn", "start"]

62
README.md

@ -0,0 +1,62 @@
<h1 align="center">Welcome to TP on CD 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)

6
compodoc.server.json

@ -0,0 +1,6 @@
{
"port": 9911,
"name": "TP_CI SERVER",
"output": "docs",
"tsconfig": "./tsconfig.json"
}

9
dev/pg.yml

@ -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

7
example.env

@ -0,0 +1,7 @@
## LOGGER
LOG_LEVEL=debug
LOG_SQL_REQUEST=false
PORT=3000
## DB [TypeORM]
DATABASE_URL=postgres://tpCi:someNotSecurePassword@localhost:5432/tpCi

4
nest-cli.json

@ -0,0 +1,4 @@
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

79
package.json

@ -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"
}
}

22
src/app.controller.spec.ts

@ -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!');
});
});
});

12
src/app.controller.ts

@ -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();
}
}

28
src/app.module.ts

@ -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 {}

8
src/app.service.ts

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

9
src/config/config.module.ts

@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
@Global()
@Module({
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}

102
src/config/config.service.spec.ts

@ -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);
});
});

68
src/config/config.service.ts

@ -0,0 +1,68 @@
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'),
PORT: z.string()
.transform(value => parseInt(value))
.default('3000'),
});
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;
}
get port(): number {
return this.envConfig.PORT;
}
}

12
src/main.ts

@ -0,0 +1,12 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigModule } from './config/config.module';
import { ConfigService } from './config/config.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const connfigService = app.select(ConfigModule).get(ConfigService);
await app.listen(connfigService.port);
console.log(`Application is listening on port ${connfigService.port}.`)
}
bootstrap();

28
test/app.e2e-spec.ts

@ -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!');
});
});

9
test/jest-e2e.json

@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

4
tsconfig.build.json

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

16
tsconfig.json

@ -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

Loading…
Cancel
Save