refactor(express): express set as default backend

This commit is contained in:
2024-10-29 23:57:47 +01:00
parent dcd1debc73
commit 15fb5a3163
66 changed files with 861 additions and 11559 deletions

View File

@@ -1,144 +0,0 @@
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
{
"name": "express-backend",
"version": "1.0.0",
"scripts": {
"dev": "nodemon src/app.ts",
"start": "ts-node src/app.ts",
"build": "tsc",
"serve": "node dist/app.js"
},
"author": "Daniel Heras Quesada",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/node": "^22.7.4",
"express": "^4.21.0",
"nodemon": "^3.1.7",
"ts-node": "^10.9.2",
"typescript": "^5.6.2"
},
"dependencies": {
"@types/bcrypt": "^5.0.2",
"@types/jsonwebtoken": "^9.0.7",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.11.3"
}
}

View File

@@ -1,17 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

View File

@@ -1,25 +0,0 @@
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',
},
};

154
back/.gitignore vendored
View File

@@ -1,39 +1,80 @@
# compiled output
/dist
/node_modules
/build
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# OS
.DS_Store
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Tests
/coverage
/.nyc_output
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
@@ -42,15 +83,62 @@ lerna-debug.log*
.env.production.local
.env.local
# temp directory
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Docusaurus cache and generated files
.docusaurus
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
### Node Patch ###
# Serverless Webpack directories
.webpack/
# Optional stylelint cache
# SvelteKit build / generate output
.svelte-kit
# End of https://www.toptal.com/developers/gitignore/api/node

View File

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

View File

@@ -1,23 +0,0 @@
# 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"]

View File

@@ -1,21 +0,0 @@
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.

View File

@@ -1,67 +0,0 @@
## 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

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

View File

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

8796
back/package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,78 +1,31 @@
{
"name": "back-nest-archetype",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"name": "express-backend",
"version": "1.0.0",
"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"
"dev": "nodemon src/app.ts",
"start": "ts-node src/app.ts",
"build": "tsc",
"serve": "node dist/app.js"
},
"author": "Daniel Heras Quesada",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/node": "^22.7.4",
"express": "^4.21.0",
"nodemon": "^3.1.7",
"ts-node": "^10.9.2",
"typescript": "^5.6.2"
},
"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"
"@types/jsonwebtoken": "^9.0.7",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"jsonwebtoken": "^9.0.2",
"mysql2": "^3.11.3"
}
}

View File

@@ -1,22 +0,0 @@
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

@@ -1,18 +0,0 @@
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

@@ -1,41 +0,0 @@
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

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

View File

View File

@@ -1,18 +0,0 @@
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

@@ -1,35 +0,0 @@
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

@@ -1,8 +0,0 @@
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

@@ -1,60 +0,0 @@
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

@@ -1,30 +0,0 @@
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

@@ -1,18 +0,0 @@
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

@@ -1,33 +0,0 @@
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

@@ -1,5 +0,0 @@
//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

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
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

@@ -1,20 +0,0 @@
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

@@ -1,49 +0,0 @@
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

@@ -1,12 +0,0 @@
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

@@ -1,18 +0,0 @@
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

@@ -1,36 +0,0 @@
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);
}
}

View File

@@ -1,9 +0,0 @@
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

@@ -1,22 +0,0 @@
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

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

View File

@@ -1,11 +0,0 @@
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

@@ -1,18 +0,0 @@
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

@@ -1,53 +0,0 @@
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

@@ -1,24 +0,0 @@
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

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

View File

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

View File

@@ -1,21 +1,17 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}