commit
11004ab2af
28 changed files with 8087 additions and 0 deletions
-
24.eslintrc.js
-
37.gitignore
-
4.prettierrc
-
47README.md
-
6compodoc.server.json
-
9dev/pg.yml
-
7example.env
-
4nest-cli.json
-
80package.json
-
22src/app.controller.spec.ts
-
20src/app.controller.ts
-
30src/app.module.ts
-
8src/app.service.ts
-
9src/config/config.module.ts
-
102src/config/config.service.spec.ts
-
69src/config/config.service.ts
-
12src/main.ts
-
6src/todos/dto/create-todo.dto.ts
-
4src/todos/dto/update-todo.dto.ts
-
13src/todos/entities/todo.entity.ts
-
49src/todos/todos.controller.ts
-
12src/todos/todos.module.ts
-
49src/todos/todos.service.ts
-
72test/app.e2e-spec.ts
-
9test/jest-e2e.json
-
4tsconfig.build.json
-
16tsconfig.json
-
7363yarn.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,47 @@ |
|||||
|
<h1 align="center">Welcome to 2021-2022-devops-final-backend 👋</h1> |
||||
|
|
||||
|
> The back end to my new startup idea |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
```bash |
||||
|
$ yarn install |
||||
|
``` |
||||
|
|
||||
|
## Running the app |
||||
|
|
||||
|
```bash |
||||
|
# development |
||||
|
yarn start |
||||
|
|
||||
|
# watch mode |
||||
|
yarn start:dev |
||||
|
|
||||
|
# production mode |
||||
|
$ yarn build |
||||
|
&& |
||||
|
$ 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 |
||||
|
|
||||
|
# Lint code |
||||
|
$ yarn lint |
||||
|
``` |
||||
@ -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,7 @@ |
|||||
|
## LOGGER |
||||
|
LOG_LEVEL=debug |
||||
|
LOG_SQL_REQUEST=false |
||||
|
PORT=3000 |
||||
|
|
||||
|
## DB [TypeORM] |
||||
|
DATABASE_URL=postgres://tpCi:someNotSecurePassword@localhost:5432/tpCi |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"collection": "@nestjs/schematics", |
||||
|
"sourceRoot": "src" |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
{ |
||||
|
"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/mapped-types": "*", |
||||
|
"@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,20 @@ |
|||||
|
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(); |
||||
|
} |
||||
|
|
||||
|
@Get('env') |
||||
|
getEnv() { |
||||
|
return { |
||||
|
...process.env, |
||||
|
DATABASE_URL: 'URL', |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
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'; |
||||
|
import { TodosModule } from './todos/todos.module'; |
||||
|
|
||||
|
@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], |
||||
|
}), |
||||
|
TodosModule, |
||||
|
], |
||||
|
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,69 @@ |
|||||
|
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; |
||||
|
} |
||||
|
} |
||||
@ -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, { cors: true }); |
||||
|
const connfigService = app.select(ConfigModule).get(ConfigService); |
||||
|
await app.listen(connfigService.port); |
||||
|
console.log(`Application is listening on port ${connfigService.port}.`); |
||||
|
} |
||||
|
bootstrap(); |
||||
@ -0,0 +1,6 @@ |
|||||
|
import { OmitType, PartialType } from '@nestjs/mapped-types'; |
||||
|
import { Todo } from '../entities/todo.entity'; |
||||
|
|
||||
|
export class CreateTodoDto extends PartialType( |
||||
|
OmitType(Todo, ['id'] as const), |
||||
|
) {} |
||||
@ -0,0 +1,4 @@ |
|||||
|
import { PartialType } from '@nestjs/mapped-types'; |
||||
|
import { CreateTodoDto } from './create-todo.dto'; |
||||
|
|
||||
|
export class UpdateTodoDto extends PartialType(CreateTodoDto) {} |
||||
@ -0,0 +1,13 @@ |
|||||
|
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; |
||||
|
|
||||
|
@Entity() |
||||
|
export class Todo { |
||||
|
@PrimaryGeneratedColumn('uuid') |
||||
|
id: string; |
||||
|
|
||||
|
@Column('text') |
||||
|
title: string; |
||||
|
|
||||
|
@Column('boolean') |
||||
|
isDone: boolean; |
||||
|
} |
||||
@ -0,0 +1,49 @@ |
|||||
|
import { |
||||
|
Controller, |
||||
|
Get, |
||||
|
Post, |
||||
|
Body, |
||||
|
Patch, |
||||
|
Param, |
||||
|
Delete, |
||||
|
HttpCode, |
||||
|
} from '@nestjs/common'; |
||||
|
import { TodosService } from './todos.service'; |
||||
|
import { CreateTodoDto } from './dto/create-todo.dto'; |
||||
|
import { UpdateTodoDto } from './dto/update-todo.dto'; |
||||
|
|
||||
|
@Controller('todos') |
||||
|
export class TodosController { |
||||
|
constructor(private readonly todosService: TodosService) {} |
||||
|
|
||||
|
@Post() |
||||
|
create(@Body() createTodoDto: CreateTodoDto) { |
||||
|
return this.todosService.create(createTodoDto); |
||||
|
} |
||||
|
|
||||
|
@Get() |
||||
|
findAll() { |
||||
|
return this.todosService.findAll(); |
||||
|
} |
||||
|
|
||||
|
@Get(':id') |
||||
|
findOne(@Param('id') id: string) { |
||||
|
return this.todosService.findOne(id); |
||||
|
} |
||||
|
|
||||
|
@Post('delete-all') |
||||
|
@HttpCode(200) |
||||
|
deleteAll() { |
||||
|
return this.todosService.deleteAll(); |
||||
|
} |
||||
|
|
||||
|
@Patch(':id') |
||||
|
update(@Param('id') id: string, @Body() updateTodoDto: UpdateTodoDto) { |
||||
|
return this.todosService.update(id, updateTodoDto); |
||||
|
} |
||||
|
|
||||
|
@Delete(':id') |
||||
|
remove(@Param('id') id: string) { |
||||
|
return this.todosService.remove(id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import { Module } from '@nestjs/common'; |
||||
|
import { TodosService } from './todos.service'; |
||||
|
import { TodosController } from './todos.controller'; |
||||
|
import { TypeOrmModule } from '@nestjs/typeorm'; |
||||
|
import { Todo } from './entities/todo.entity'; |
||||
|
|
||||
|
@Module({ |
||||
|
imports: [TypeOrmModule.forFeature([Todo])], |
||||
|
controllers: [TodosController], |
||||
|
providers: [TodosService], |
||||
|
}) |
||||
|
export class TodosModule {} |
||||
@ -0,0 +1,49 @@ |
|||||
|
import { Injectable } from '@nestjs/common'; |
||||
|
import { CreateTodoDto } from './dto/create-todo.dto'; |
||||
|
import { UpdateTodoDto } from './dto/update-todo.dto'; |
||||
|
import { Repository } from 'typeorm'; |
||||
|
import { InjectRepository } from '@nestjs/typeorm'; |
||||
|
import { Todo } from './entities/todo.entity'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class TodosService { |
||||
|
constructor( |
||||
|
@InjectRepository(Todo) |
||||
|
private usersRepository: Repository<Todo>, |
||||
|
) {} |
||||
|
|
||||
|
create(createTodoDto: CreateTodoDto) { |
||||
|
const todo = new Todo(); |
||||
|
todo.isDone = createTodoDto.isDone ?? false; |
||||
|
todo.title = createTodoDto.title; |
||||
|
return this.usersRepository.save(todo); |
||||
|
} |
||||
|
|
||||
|
findAll() { |
||||
|
return this.usersRepository.find(); |
||||
|
} |
||||
|
|
||||
|
findOne(id: string) { |
||||
|
return this.usersRepository.findOne({ id }); |
||||
|
} |
||||
|
|
||||
|
async update(id: string, updateTodoDto: UpdateTodoDto) { |
||||
|
await this.usersRepository.update( |
||||
|
{ |
||||
|
id, |
||||
|
}, |
||||
|
{ title: updateTodoDto.title, isDone: updateTodoDto.isDone }, |
||||
|
); |
||||
|
return this.findOne(id); |
||||
|
} |
||||
|
|
||||
|
async remove(id: string) { |
||||
|
const previous = await this.findOne(id); |
||||
|
await this.usersRepository.delete({ id }); |
||||
|
return previous; |
||||
|
} |
||||
|
|
||||
|
deleteAll() { |
||||
|
return this.usersRepository.delete({}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
import { Test, TestingModule } from '@nestjs/testing'; |
||||
|
import { INestApplication } from '@nestjs/common'; |
||||
|
import * as request from 'supertest'; |
||||
|
import { AppModule } from './../src/app.module'; |
||||
|
|
||||
|
describe('App (e2e)', () => { |
||||
|
let app: INestApplication; |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({ |
||||
|
imports: [AppModule], |
||||
|
}).compile(); |
||||
|
|
||||
|
app = moduleFixture.createNestApplication(); |
||||
|
await app.init(); |
||||
|
}); |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
await request(app.getHttpServer()).post('/todos/delete-all').expect(200); |
||||
|
}); |
||||
|
|
||||
|
afterEach(async () => { |
||||
|
await request(app.getHttpServer()).post('/todos/delete-all').expect(200); |
||||
|
await app.close(); |
||||
|
}); |
||||
|
|
||||
|
it('should allow for creation, edition, suppression and listing of todos', async () => { |
||||
|
const created = await request(app.getHttpServer()) |
||||
|
.post('/todos') |
||||
|
.send({ title: 'My todo', isDone: false }) |
||||
|
.expect(201); |
||||
|
expect(created.body.title).toEqual('My todo'); |
||||
|
expect(created.body.isDone).toEqual(false); |
||||
|
|
||||
|
const uuid = created.body.id; |
||||
|
|
||||
|
const path = `/todos/${uuid}`; |
||||
|
|
||||
|
await request(app.getHttpServer()) |
||||
|
.get('/todos') |
||||
|
.expect(200) |
||||
|
.expect([created.body]); |
||||
|
|
||||
|
await request(app.getHttpServer()) |
||||
|
.get(path) |
||||
|
.expect(200) |
||||
|
.expect(created.body); |
||||
|
|
||||
|
const newTodos = await request(app.getHttpServer()) |
||||
|
.patch(path) |
||||
|
.send({ isDone: true, title: 'New title' }) |
||||
|
.expect(200) |
||||
|
.expect({ id: uuid, isDone: true, title: 'New title' }); |
||||
|
|
||||
|
await request(app.getHttpServer()) |
||||
|
.get(path) |
||||
|
.expect(200) |
||||
|
.expect(newTodos.body); |
||||
|
|
||||
|
await request(app.getHttpServer()) |
||||
|
.delete(path) |
||||
|
.expect(200) |
||||
|
.expect(newTodos.body); |
||||
|
}); |
||||
|
|
||||
|
it('/ hello world', () => { |
||||
|
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 |
||||
|
} |
||||
|
} |
||||
7363
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