feat: both nest and next backends

This commit is contained in:
2024-12-28 20:37:20 +01:00
parent ed3e59e079
commit dfa3d1c40a
68 changed files with 10699 additions and 1 deletions

View File

View File

View File

View File

View File

25
back-nestjs/.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
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',
},
};

56
back-nestjs/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# 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
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

4
back-nestjs/.prettierrc Normal file
View File

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

23
back-nestjs/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# Use the official Node.js slim image as a base image
FROM node:18-slim
# Set the working directory inside the container
WORKDIR /app
# Copy package.json and package-lock.json to the working directory
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code to the working directory
COPY . .
# Build the NestJS application
RUN npm run build
# Expose the port that the NestJS application runs on
EXPOSE 3000
# Start the NestJS application
CMD ["npm", "run", "start:prod"]

21
back-nestjs/LICENCE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Daniel Heras Quesada
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

67
back-nestjs/README.md Normal file
View File

@@ -0,0 +1,67 @@
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript archetype repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Authentication functionality
All done with the `Auth` decorator, which lets you determine a list of authorized roles.
The are 3 available roles:
- Public: no role nor user needed
- User
- Admin
These can be extended by simply adding new elements to the `src/users/roles/role.enum.ts` enumeration.
```ts
@Auth(Role.Public)
@Get()
getHello(): string { return "Hello!" }
```
### JWT
User login is managed by a `JWT` based structure. The client must send both username and password in the body of a `POST` method at `/auth/login`, which would return a `JWT` in case of success. This token must be used to interact with the server on every non-public route. Internally the server assumes the user passwords are stored hashed on a [Mariadb](https://mariadb.org/) database.
## Stay in touch
- Author - [Daniel Heras Quesada](https://dqnid.com)
- Twitter - [@nestframework](https://twitter.com/nestframework)
- Linkedin - [daniel-heras-quesada](https://www.linkedin.com/in/daniel-heras-quesada/)
## License
This archetype is [MIT licensed](LICENSE).

View File

@@ -0,0 +1,10 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: nestjs-app
ports:
- '3000:3000'

View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

9774
back-nestjs/package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

78
back-nestjs/package.json Normal file
View File

@@ -0,0 +1,78 @@
{
"name": "back-nest-archetype",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"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",
"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": "^10.0.0",
"@nestjs/config": "^3.2.3",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/throttler": "^5.1.2",
"@nestjs/typeorm": "^10.0.2",
"bcrypt": "^5.1.1",
"mysql2": "^3.9.7",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.0.0",
"@types/bcrypt": "^5.0.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

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

View File

@@ -0,0 +1,18 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Throttle } from '@nestjs/throttler';
import { Auth } from './auth/auth.decorator';
import { Role } from './users/roles/role.enum';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
// Override default configuration for Rate limiting and duration.
@Throttle({ default: { limit: 10, ttl: 1000 } })
@Auth(Role.Public)
@Get()
getHello(): string {
return this.appService.getHello();
}
}

View File

@@ -0,0 +1,41 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { ThrottlerModule } from '@nestjs/throttler';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { ExampleModule } from './example/example.module';
import { User } from './users/entities/user.entity';
import { Example } from './example/entities/example.entity';
@Module({
imports: [
ConfigModule.forRoot(),
AuthModule,
UsersModule,
ExampleModule,
ThrottlerModule.forRoot([
{
ttl: 10000,
limit: 20,
},
]),
TypeOrmModule.forRoot({
type: process.env.DB_TYPE as 'mysql' | 'mariadb',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_MAIN,
entities: [User, Example],
synchronize: false,
connectTimeout: 20000,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

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

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from './auth.controller';
describe('AuthController', () => {
let controller: AuthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [AuthController],
}).compile();
controller = module.get<AuthController>(AuthController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,35 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
Request,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { Role } from 'src/users/roles/role.enum';
import { Auth } from './auth.decorator';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Auth(Role.Public)
@HttpCode(HttpStatus.OK)
@Post('login')
signIn(@Body() signInDto: Record<string, any>) {
return this.authService.signIn(signInDto.username, signInDto.password);
}
@Auth(Role.Admin)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
@Get('options')
getOptions(@Request() req) {
return req.roles;
}
}

View File

@@ -0,0 +1,8 @@
import { SetMetadata, applyDecorators } from '@nestjs/common';
import { Role } from 'src/users/roles/role.enum';
export const ROLES_METADATA = 'roles';
export function Auth(...roles: Role[]) {
return applyDecorators(SetMetadata(ROLES_METADATA, roles));
}

View File

@@ -0,0 +1,60 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { Request } from 'express';
import { Reflector } from '@nestjs/core';
import { ROLES_METADATA } from './auth.decorator';
import { Role } from 'src/users/roles/role.enum';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private reflector: Reflector,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const decorator_roles = this.reflector.getAllAndOverride<Role[]>(
ROLES_METADATA,
[context.getHandler(), context.getClass()],
);
if (decorator_roles && decorator_roles.includes(Role.Public)) {
return true;
}
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: jwtConstants.secret,
});
// 💡 We're assigning the payload to the request object here
// so that we can access it in our route handlers
request['user'] = payload;
const found = payload.roles.some((r: Role) =>
decorator_roles.includes(r),
);
if (!found) {
throw new UnauthorizedException();
}
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}

View File

@@ -0,0 +1,30 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { jwtConstants } from './constants';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth.guard';
//TODO: TIMING LOGIC: ID Token: 60 minutes. Access Token: 60 minutes. Refresh Token: 90 days
@Module({
imports: [
UsersModule,
JwtModule.register({
global: true,
secret: jwtConstants.secret,
signOptions: { expiresIn: '60m' },
}),
],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}

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

View File

@@ -0,0 +1,33 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from 'src/users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async signIn(
username: string,
pass: string,
): Promise<{ access_token: string }> {
console.log(username, pass);
const user = await this.usersService.findOne(username);
if (!user) throw new UnauthorizedException();
const isSamePasswd = await bcrypt.compare(`${pass}`, `${user?.password}`);
if (!isSamePasswd) throw new UnauthorizedException();
const payload = {
sub: user.id,
username: user.username,
roles: user.roles,
picture: user.picture,
};
return { access_token: await this.jwtService.signAsync(payload) };
}
}

View File

@@ -0,0 +1,5 @@
//TODO: remove this and do it propertly
export const jwtConstants = {
secret:
'DO NOT USE THIS VALUE. INSTEAD, CREATE A COMPLEX SECRET AND KEEP IT SAFE OUTSIDE OF THE SOURCE CODE.',
};

View File

@@ -0,0 +1 @@
export class CreateExampleDto {}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateExampleDto } from './create-example.dto';
export class UpdateExampleDto extends PartialType(CreateExampleDto) {}

View File

@@ -0,0 +1,19 @@
import { Column, Entity, Generated, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Example {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
image: string;
@Column()
created_at: string;
}

View File

@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ExampleController } from './example.controller';
import { ExampleService } from './example.service';
describe('ExampleController', () => {
let controller: ExampleController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ExampleController],
providers: [ExampleService],
}).compile();
controller = module.get<ExampleController>(ExampleController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,49 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { ExampleService } from './example.service';
import { CreateExampleDto } from './dto/create-example.dto';
import { UpdateExampleDto } from './dto/update-example.dto';
import { Auth } from 'src/auth/auth.decorator';
import { Role } from 'src/users/roles/role.enum';
@Controller('example')
export class ExampleController {
constructor(private readonly exampleService: ExampleService) {}
@Auth(Role.Admin)
@Post()
create(@Body() createExampleDto: CreateExampleDto) {
return this.exampleService.create(createExampleDto);
}
@Auth(Role.Public)
@Get()
findAll() {
return this.exampleService.findAll();
}
@Auth(Role.User)
@Get(':id')
findOne(@Param('id') id: string) {
return this.exampleService.findOne(+id);
}
@Auth(Role.Admin)
@Patch(':id')
update(@Param('id') id: string, @Body() updateExampleDto: UpdateExampleDto) {
return this.exampleService.update(+id, updateExampleDto);
}
@Auth(Role.Admin)
@Delete(':id')
remove(@Param('id') id: string) {
return this.exampleService.remove(+id);
}
}

View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ExampleService } from './example.service';
import { ExampleController } from './example.controller';
import { Example } from './entities/example.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([Example])],
controllers: [ExampleController],
providers: [ExampleService],
})
export class ExampleModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ExampleService } from './example.service';
describe('ExampleService', () => {
let service: ExampleService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ExampleService],
}).compile();
service = module.get<ExampleService>(ExampleService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { CreateExampleDto } from './dto/create-example.dto';
import { UpdateExampleDto } from './dto/update-example.dto';
import { Example } from './entities/example.entity';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
@Injectable()
export class ExampleService {
constructor(
@InjectRepository(Example)
private exampleRepository: Repository<Example>,
@InjectDataSource()
private dataSource: DataSource,
) {}
create(createExampleDto: CreateExampleDto) {
return this.exampleRepository.create(createExampleDto);
}
findAll() {
return this.dataSource.query('select * from example');
}
findOne(id: number) {
return this.exampleRepository.findOneBy({ id });
}
update(id: number, updateExampleDto: UpdateExampleDto) {
return this.exampleRepository.update({ id }, updateExampleDto);
}
remove(id: number) {
return this.exampleRepository.delete(id);
}
}

9
back-nestjs/src/main.ts Normal file
View File

@@ -0,0 +1,9 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(3000);
}
bootstrap();

View File

@@ -0,0 +1,22 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
// TODO: username should be unique and, maybe can act as ID?
// NOTE: CREATE TABLE user (id bigint primary key DEFAULT UUID_SHORT(), username char(20) not null, password char(20) not null, roles char(50) not null);
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@Column()
roles: string;
@Column()
picture: string;
}

View File

@@ -0,0 +1,6 @@
export enum Role {
Public = 'public',
User = 'user',
Manager = 'manager',
Admin = 'admin',
}

View File

@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,53 @@
import { Injectable } from '@nestjs/common';
import { Role } from './roles/role.enum';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { DataSource, Repository } from 'typeorm';
export type UserType = {
id: number;
username: string;
password: string;
roles: Role[];
picture: string;
};
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
@InjectDataSource()
private dataSource: DataSource,
) {}
async findOne(username: string): Promise<UserType | undefined> {
const db_user = await this.usersRepository.findOneBy({ username });
if (!db_user) return null;
//TODO: change this shabby mapping for a more adequate database structure
const user: UserType = {
id: db_user.id,
username: db_user.username,
password: db_user.password,
roles: db_user.roles.split(';') as Role[],
picture: db_user.picture,
};
console.log(user);
return user;
}
async doSOmething() {
this.dataSource.query('SELECT * from users');
}
// async create(
// username: string,
// password: string,
// roles: Role[],
// ): Promise<User | undefined> {
// const roles_string = roles.join(';');
// const create_result = this.usersRepository.create(
// new User(username, password, roles_string),
// );
// }
}

View File

@@ -0,0 +1,24 @@
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();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

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

View File

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

21
back-nestjs/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

View File

@@ -27,7 +27,7 @@ services:
backend:
build:
context: ./back
context: ./back-express
dockerfile: Dockerfile
container_name: nestjs-app
ports: