feat: both nest and next backends
This commit is contained in:
1 parent
ed3e59e079
commit
dfa3d1c40a
68 files changed
+10699
-1
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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',
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -0,0 +1,10 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: nestjs-app
|
||||
ports:
|
||||
- '3000:3000'
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true
|
||||
}
|
||||
}
|
||||
Generated
+9774
File diff suppressed because it is too large.
Load diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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,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();
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
@@ -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.',
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export class CreateExampleDto {}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateExampleDto } from './create-example.dto';
|
||||
|
||||
export class UpdateExampleDto extends PartialType(CreateExampleDto) {}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum Role {
|
||||
Public = 'public',
|
||||
User = 'user',
|
||||
Manager = 'manager',
|
||||
Admin = 'admin',
|
||||
}
|
||||
@@ -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 {}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
@@ -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!');
|
||||
});
|
||||
});
|
||||
@@ -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,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
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ services:
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./back
|
||||
context: ./back-express
|
||||
dockerfile: Dockerfile
|
||||
container_name: nestjs-app
|
||||
ports:
|
||||
|
||||
Reference in new issue
Block a user