add cursor
This commit is contained in:
8
.cursor/rules/nestjs-core-module-guidelines.mdc.mdc
Normal file
8
.cursor/rules/nestjs-core-module-guidelines.mdc.mdc
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: Enforces specific guidelines for the core module in NestJS, focusing on global filters, middleware, guards, and interceptors.
|
||||
globs: src/core/**/*.*
|
||||
---
|
||||
- Global filters for exception handling.
|
||||
- Global middlewares for request management.
|
||||
- Guards for permission management.
|
||||
- Interceptors for request management.
|
||||
26
.cursor/rules/nestjs-general-guidelines.mdc.mdc
Normal file
26
.cursor/rules/nestjs-general-guidelines.mdc.mdc
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
description: Specifies NestJS-specific architectural principles, modular design, and testing practices within the 'src' directory.
|
||||
globs: src/**/*.*
|
||||
---
|
||||
- Use modular architecture
|
||||
- Encapsulate the API in modules.
|
||||
- One module per main domain/route.
|
||||
- One controller for its route.
|
||||
- And other controllers for secondary routes.
|
||||
- A models folder with data types.
|
||||
- DTOs validated with class-validator for inputs.
|
||||
- Declare simple types for outputs.
|
||||
- A services module with business logic and persistence.
|
||||
- One service per entity.
|
||||
- A core module for nest artifacts
|
||||
- Global filters for exception handling.
|
||||
- Global middlewares for request management.
|
||||
- Guards for permission management.
|
||||
- Interceptors for request management.
|
||||
- A shared module for services shared between modules.
|
||||
- Utilities
|
||||
- Shared business logic
|
||||
- Use the standard Jest framework for testing.
|
||||
- Write tests for each controller and service.
|
||||
- Write end to end tests for each api module.
|
||||
- Add a admin/test method to each controller as a smoke test.
|
||||
12
.cursor/rules/nestjs-module-structure-guidelines.mdc
Normal file
12
.cursor/rules/nestjs-module-structure-guidelines.mdc
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
description: Prescribes the structure and components within NestJS modules, including controllers, models, DTOs, and services, ensuring API encapsulation.
|
||||
globs: src/modules/**/*.*
|
||||
---
|
||||
- One module per main domain/route.
|
||||
- One controller for its route.
|
||||
- And other controllers for secondary routes.
|
||||
- A models folder with data types.
|
||||
- DTOs validated with class-validator for inputs.
|
||||
- Declare simple types for outputs.
|
||||
- A services module with business logic and persistence.
|
||||
- One service per entity.
|
||||
6
.cursor/rules/nestjs-shared-module-guidelines.mdc
Normal file
6
.cursor/rules/nestjs-shared-module-guidelines.mdc
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
description: Defines standards for the shared module in NestJS, emphasizing utilities and shared business logic accessible across modules.
|
||||
globs: src/shared/**/*.*
|
||||
---
|
||||
- Utilities
|
||||
- Shared business logic
|
||||
8
.cursor/rules/nestjs-testing-guidelines.mdc
Normal file
8
.cursor/rules/nestjs-testing-guidelines.mdc
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
description: Sets standards for testing NestJS applications, including unit, integration, and end-to-end tests, plus the use of Jest.
|
||||
globs: **/*.spec.ts
|
||||
---
|
||||
- Use the standard Jest framework for testing.
|
||||
- Write tests for each controller and service.
|
||||
- Write end to end tests for each api module.
|
||||
- Add a admin/test method to each controller as a smoke test.
|
||||
66
.cursor/rules/typescript-general-guidelines.mdc
Normal file
66
.cursor/rules/typescript-general-guidelines.mdc
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
description: Applies general TypeScript coding standards across the project, including naming conventions, function structure, data handling, and exception handling.
|
||||
globs: **/*.ts
|
||||
---
|
||||
- Use English for all code and documentation.
|
||||
- Always declare the type of each variable and function (parameters and return value).
|
||||
- Avoid using any.
|
||||
- Create necessary types.
|
||||
- Use JSDoc to document public classes and methods.
|
||||
- Don't leave blank lines within a function.
|
||||
- One export per file.
|
||||
- Use PascalCase for classes.
|
||||
- Use camelCase for variables, functions, and methods.
|
||||
- Use kebab-case for file and directory names.
|
||||
- Use UPPERCASE for environment variables.
|
||||
- Avoid magic numbers and define constants.
|
||||
- Start each function with a verb.
|
||||
- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
|
||||
- Use complete words instead of abbreviations and correct spelling.
|
||||
- Except for standard abbreviations like API, URL, etc.
|
||||
- Except for well-known abbreviations:
|
||||
- i, j for loops
|
||||
- err for errors
|
||||
- ctx for contexts
|
||||
- req, res, next for middleware function parameters
|
||||
- Write short functions with a single purpose. Less than 20 instructions.
|
||||
- Name functions with a verb and something else.
|
||||
- If it returns a boolean, use isX or hasX, canX, etc.
|
||||
- If it doesn't return anything, use executeX or saveX, etc.
|
||||
- Avoid nesting blocks by:
|
||||
- Early checks and returns.
|
||||
- Extraction to utility functions.
|
||||
- Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting.
|
||||
- Use arrow functions for simple functions (less than 3 instructions).
|
||||
- Use named functions for non-simple functions.
|
||||
- Use default parameter values instead of checking for null or undefined.
|
||||
- Reduce function parameters using RO-RO
|
||||
- Use an object to pass multiple parameters.
|
||||
- Use an object to return results.
|
||||
- Declare necessary types for input arguments and output.
|
||||
- Use a single level of abstraction.
|
||||
- Don't abuse primitive types and encapsulate data in composite types.
|
||||
- Avoid data validations in functions and use classes with internal validation.
|
||||
- Prefer immutability for data.
|
||||
- Use readonly for data that doesn't change.
|
||||
- Use as const for literals that don't change.
|
||||
- Follow SOLID principles.
|
||||
- Prefer composition over inheritance.
|
||||
- Declare interfaces to define contracts.
|
||||
- Write small classes with a single purpose.
|
||||
- Less than 200 instructions.
|
||||
- Less than 10 public methods.
|
||||
- Less than 10 properties.
|
||||
- Use exceptions to handle errors you don't expect.
|
||||
- If you catch an exception, it should be to:
|
||||
- Fix an expected problem.
|
||||
- Add context.
|
||||
- Otherwise, use a global handler.
|
||||
- Follow the Arrange-Act-Assert convention for tests.
|
||||
- Name test variables clearly.
|
||||
- Follow the convention: inputX, mockX, actualX, expectedX, etc.
|
||||
- Write unit tests for each public function.
|
||||
- Use test doubles to simulate dependencies.
|
||||
- Except for third-party dependencies that are not expensive to execute.
|
||||
- Write acceptance tests for each module.
|
||||
- Follow the Given-When-Then convention.
|
||||
251
.cursorrules
Normal file
251
.cursorrules
Normal file
@@ -0,0 +1,251 @@
|
||||
You are a senior TypeScript programmer with experience in the NestJS framework and a preference for clean programming and design patterns. Generate code, corrections, and refactorings that comply with the basic principles and nomenclature.
|
||||
|
||||
## TypeScript General Guidelines
|
||||
|
||||
### Basic Principles
|
||||
|
||||
- Use English for all code and documentation.
|
||||
- Always declare the type of each variable and function (parameters and return value).
|
||||
- Avoid using any.
|
||||
- Create necessary types.
|
||||
- Use JSDoc to document public classes and methods.
|
||||
- Don't leave blank lines within a function.
|
||||
- One export per file.
|
||||
|
||||
### Nomenclature
|
||||
|
||||
- Use PascalCase for classes.
|
||||
- Use camelCase for variables, functions, and methods.
|
||||
- Use kebab-case for file and directory names.
|
||||
- Use UPPERCASE for environment variables.
|
||||
- Avoid magic numbers and define constants.
|
||||
- Start each function with a verb.
|
||||
- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
|
||||
- Use complete words instead of abbreviations and correct spelling.
|
||||
- Except for standard abbreviations like API, URL, etc.
|
||||
- Except for well-known abbreviations:
|
||||
- i, j for loops
|
||||
- err for errors
|
||||
- ctx for contexts
|
||||
- req, res, next for middleware function parameters
|
||||
|
||||
### Functions
|
||||
|
||||
- In this context, what is understood as a function will also apply to a method.
|
||||
- Write short functions with a single purpose. Less than 20 instructions.
|
||||
- Name functions with a verb and something else.
|
||||
- If it returns a boolean, use isX or hasX, canX, etc.
|
||||
- If it doesn't return anything, use executeX or saveX, etc.
|
||||
- Avoid nesting blocks by:
|
||||
- Early checks and returns.
|
||||
- Extraction to utility functions.
|
||||
- Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting.
|
||||
- Use arrow functions for simple functions (less than 3 instructions).
|
||||
- Use named functions for non-simple functions.
|
||||
- Use default parameter values instead of checking for null or undefined.
|
||||
- Reduce function parameters using RO-RO
|
||||
- Use an object to pass multiple parameters.
|
||||
- Use an object to return results.
|
||||
- Declare necessary types for input arguments and output.
|
||||
- Use a single level of abstraction.
|
||||
|
||||
### Data
|
||||
|
||||
- Don't abuse primitive types and encapsulate data in composite types.
|
||||
- Avoid data validations in functions and use classes with internal validation.
|
||||
- Prefer immutability for data.
|
||||
- Use readonly for data that doesn't change.
|
||||
- Use as const for literals that don't change.
|
||||
|
||||
### Classes
|
||||
|
||||
- Follow SOLID principles.
|
||||
- Prefer composition over inheritance.
|
||||
- Declare interfaces to define contracts.
|
||||
- Write small classes with a single purpose.
|
||||
- Less than 200 instructions.
|
||||
- Less than 10 public methods.
|
||||
- Less than 10 properties.
|
||||
|
||||
### Exceptions
|
||||
|
||||
- Use exceptions to handle errors you don't expect.
|
||||
- If you catch an exception, it should be to:
|
||||
- Fix an expected problem.
|
||||
- Add context.
|
||||
- Otherwise, use a global handler.
|
||||
|
||||
### Testing
|
||||
|
||||
- Follow the Arrange-Act-Assert convention for tests.
|
||||
- Name test variables clearly.
|
||||
- Follow the convention: inputX, mockX, actualX, expectedX, etc.
|
||||
- Write unit tests for each public function.
|
||||
- Use test doubles to simulate dependencies.
|
||||
- Except for third-party dependencies that are not expensive to execute.
|
||||
- Write acceptance tests for each module.
|
||||
- Follow the Given-When-Then convention.
|
||||
|
||||
## Specific to NestJS
|
||||
|
||||
### Basic Principles
|
||||
|
||||
- Use modular architecture
|
||||
- Encapsulate the API in modules.
|
||||
- One module per main domain/route.
|
||||
- One controller for its route.
|
||||
- And other controllers for secondary routes.
|
||||
- A models folder with data types.
|
||||
- DTOs validated with class-validator for inputs.
|
||||
- Declare simple types for outputs.
|
||||
- A services module with business logic and persistence.
|
||||
- One service per entity.
|
||||
- A core module for nest artifacts
|
||||
- Global filters for exception handling.
|
||||
- Global middlewares for request management.
|
||||
- Guards for permission management.
|
||||
- Interceptors for request management.
|
||||
- A shared module for services shared between modules.
|
||||
- Utilities
|
||||
- Shared business logic
|
||||
|
||||
### Testing
|
||||
|
||||
- Use the standard Jest framework for testing.
|
||||
- Write tests for each controller and service.
|
||||
- Write end to end tests for each api module.
|
||||
- Add a admin/test method to each controller as a smoke test.
|
||||
|
||||
|
||||
---
|
||||
description: Enforces specific guidelines for the core module in NestJS, focusing on global filters, middleware, guards, and interceptors.
|
||||
globs: src/core/**/*.*
|
||||
---
|
||||
- Global filters for exception handling.
|
||||
- Global middlewares for request management.
|
||||
- Guards for permission management.
|
||||
- Interceptors for request management.
|
||||
|
||||
---
|
||||
description: Specifies NestJS-specific architectural principles, modular design, and testing practices within the 'src' directory.
|
||||
globs: src/**/*.*
|
||||
---
|
||||
- Use modular architecture
|
||||
- Encapsulate the API in modules.
|
||||
- One module per main domain/route.
|
||||
- One controller for its route.
|
||||
- And other controllers for secondary routes.
|
||||
- A models folder with data types.
|
||||
- DTOs validated with class-validator for inputs.
|
||||
- Declare simple types for outputs.
|
||||
- A services module with business logic and persistence.
|
||||
- One service per entity.
|
||||
- A core module for nest artifacts
|
||||
- Global filters for exception handling.
|
||||
- Global middlewares for request management.
|
||||
- Guards for permission management.
|
||||
- Interceptors for request management.
|
||||
- A shared module for services shared between modules.
|
||||
- Utilities
|
||||
- Shared business logic
|
||||
- Use the standard Jest framework for testing.
|
||||
- Write tests for each controller and service.
|
||||
- Write end to end tests for each api module.
|
||||
- Add a admin/test method to each controller as a smoke test.
|
||||
|
||||
---
|
||||
description: Prescribes the structure and components within NestJS modules, including controllers, models, DTOs, and services, ensuring API encapsulation.
|
||||
globs: src/modules/**/*.*
|
||||
---
|
||||
- One module per main domain/route.
|
||||
- One controller for its route.
|
||||
- And other controllers for secondary routes.
|
||||
- A models folder with data types.
|
||||
- DTOs validated with class-validator for inputs.
|
||||
- Declare simple types for outputs.
|
||||
- A services module with business logic and persistence.
|
||||
- One service per entity.
|
||||
|
||||
---
|
||||
description: Defines standards for the shared module in NestJS, emphasizing utilities and shared business logic accessible across modules.
|
||||
globs: src/shared/**/*.*
|
||||
---
|
||||
- Utilities
|
||||
- Shared business logic
|
||||
|
||||
---
|
||||
description: Sets standards for testing NestJS applications, including unit, integration, and end-to-end tests, plus the use of Jest.
|
||||
globs: **/*.spec.ts
|
||||
---
|
||||
- Use the standard Jest framework for testing.
|
||||
- Write tests for each controller and service.
|
||||
- Write end to end tests for each api module.
|
||||
- Add a admin/test method to each controller as a smoke test.
|
||||
|
||||
|
||||
---
|
||||
description: Applies general TypeScript coding standards across the project, including naming conventions, function structure, data handling, and exception handling.
|
||||
globs: **/*.ts
|
||||
---
|
||||
- Use English for all code and documentation.
|
||||
- Always declare the type of each variable and function (parameters and return value).
|
||||
- Avoid using any.
|
||||
- Create necessary types.
|
||||
- Use JSDoc to document public classes and methods.
|
||||
- Don't leave blank lines within a function.
|
||||
- One export per file.
|
||||
- Use PascalCase for classes.
|
||||
- Use camelCase for variables, functions, and methods.
|
||||
- Use kebab-case for file and directory names.
|
||||
- Use UPPERCASE for environment variables.
|
||||
- Avoid magic numbers and define constants.
|
||||
- Start each function with a verb.
|
||||
- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc.
|
||||
- Use complete words instead of abbreviations and correct spelling.
|
||||
- Except for standard abbreviations like API, URL, etc.
|
||||
- Except for well-known abbreviations:
|
||||
- i, j for loops
|
||||
- err for errors
|
||||
- ctx for contexts
|
||||
- req, res, next for middleware function parameters
|
||||
- Write short functions with a single purpose. Less than 20 instructions.
|
||||
- Name functions with a verb and something else.
|
||||
- If it returns a boolean, use isX or hasX, canX, etc.
|
||||
- If it doesn't return anything, use executeX or saveX, etc.
|
||||
- Avoid nesting blocks by:
|
||||
- Early checks and returns.
|
||||
- Extraction to utility functions.
|
||||
- Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting.
|
||||
- Use arrow functions for simple functions (less than 3 instructions).
|
||||
- Use named functions for non-simple functions.
|
||||
- Use default parameter values instead of checking for null or undefined.
|
||||
- Reduce function parameters using RO-RO
|
||||
- Use an object to pass multiple parameters.
|
||||
- Use an object to return results.
|
||||
- Declare necessary types for input arguments and output.
|
||||
- Use a single level of abstraction.
|
||||
- Don't abuse primitive types and encapsulate data in composite types.
|
||||
- Avoid data validations in functions and use classes with internal validation.
|
||||
- Prefer immutability for data.
|
||||
- Use readonly for data that doesn't change.
|
||||
- Use as const for literals that don't change.
|
||||
- Follow SOLID principles.
|
||||
- Prefer composition over inheritance.
|
||||
- Declare interfaces to define contracts.
|
||||
- Write small classes with a single purpose.
|
||||
- Less than 200 instructions.
|
||||
- Less than 10 public methods.
|
||||
- Less than 10 properties.
|
||||
- Use exceptions to handle errors you don't expect.
|
||||
- If you catch an exception, it should be to:
|
||||
- Fix an expected problem.
|
||||
- Add context.
|
||||
- Otherwise, use a global handler.
|
||||
- Follow the Arrange-Act-Assert convention for tests.
|
||||
- Name test variables clearly.
|
||||
- Follow the convention: inputX, mockX, actualX, expectedX, etc.
|
||||
- Write unit tests for each public function.
|
||||
- Use test doubles to simulate dependencies.
|
||||
- Except for third-party dependencies that are not expensive to execute.
|
||||
- Write acceptance tests for each module.
|
||||
- Follow the Given-When-Then convention.
|
||||
@@ -5,31 +5,30 @@ import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
export default tseslint.config(
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
{
|
||||
ignores: ['eslint.config.mjs'],
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
sourceType: 'commonjs',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// eslintPluginPrettierRecommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.jest,
|
||||
},
|
||||
sourceType: 'commonjs',
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn'
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'warn',
|
||||
'@typescript-eslint/no-unsafe-argument': 'warn'
|
||||
},
|
||||
},
|
||||
);
|
||||
7599
package-lock.json
generated
7599
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "nest-base",
|
||||
"name": "base_nestjs",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
@@ -20,36 +20,13 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^17.1.1",
|
||||
"@neondatabase/serverless": "^1.0.0",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/cookie-parser": "^1.4.8",
|
||||
"@types/hapi__joi": "^17.1.15",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"aws-sdk": "^2.1692.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"joi": "^17.13.3",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.15.6",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"uuid": "^11.1.0"
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
@@ -57,18 +34,16 @@
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@swc/cli": "^0.6.0",
|
||||
"@swc/core": "^1.10.7",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/multer": "^1.4.12",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest": "^30.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
|
||||
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal 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!');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,50 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import * as Joi from 'joi';
|
||||
import {UsersModule} from "./users/user.module";
|
||||
import {AuthenticationModule} from "./authentication/authentication.module";
|
||||
import { PostsModule } from './posts/posts.module';
|
||||
import { CategoriesModule } from './categories/categories.module';
|
||||
import {FilesModule} from "./files/file.module";
|
||||
import { AuthenticationModule } from './authentication/authentication.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
type: 'postgres',
|
||||
host: configService.get<string>('POSTGRES_HOST'),
|
||||
port: parseInt(configService.get<string>('POSTGRES_PORT', '5432'), 10),
|
||||
username: configService.get<string>('POSTGRES_USER'),
|
||||
password: configService.get<string>('POSTGRES_PASSWORD'),
|
||||
database: configService.get<string>('POSTGRES_DB'),
|
||||
autoLoadEntities: true,
|
||||
ssl: {
|
||||
rejectUnauthorized: false, // Needed for Neon and similar managed DBs
|
||||
},
|
||||
synchronize: true, // Disable in production
|
||||
}),
|
||||
}),
|
||||
ConfigModule.forRoot({
|
||||
validationSchema: Joi.object({
|
||||
JWT_SECRET: Joi.string().required(),
|
||||
JWT_EXPIRATION_TIME: Joi.string().required(),
|
||||
S3_BUCKET: Joi.string().required(),
|
||||
S3_ACCESS_KEY: Joi.string().required(),
|
||||
S3_ENDPOINT: Joi.string().required(),
|
||||
})
|
||||
}),
|
||||
UsersModule,
|
||||
AuthenticationModule,
|
||||
PostsModule,
|
||||
CategoriesModule,
|
||||
FilesModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
imports: [AuthenticationModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {}
|
||||
|
||||
65
src/authentication/authentication.controller.spec.ts
Normal file
65
src/authentication/authentication.controller.spec.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthenticationController } from './authentication.controller';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
|
||||
describe('AuthenticationController', () => {
|
||||
let controller: AuthenticationController;
|
||||
let service: AuthenticationService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthenticationController],
|
||||
providers: [AuthenticationService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AuthenticationController>(AuthenticationController);
|
||||
service = module.get<AuthenticationService>(AuthenticationService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should return successful login response with valid credentials', async () => {
|
||||
const inputLoginDto: LoginDto = {
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
};
|
||||
const expectedResponse = {
|
||||
message: 'Login successful',
|
||||
user: {
|
||||
email: 'test@example.com',
|
||||
},
|
||||
token: 'placeholder-token',
|
||||
};
|
||||
|
||||
const actualResponse = await controller.login(inputLoginDto);
|
||||
|
||||
expect(actualResponse).toEqual(expectedResponse);
|
||||
});
|
||||
|
||||
it('should throw UnauthorizedException with invalid credentials', async () => {
|
||||
const inputLoginDto: LoginDto = {
|
||||
email: 'invalid@example.com',
|
||||
password: 'wrongpassword',
|
||||
};
|
||||
|
||||
await expect(controller.login(inputLoginDto)).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test', () => {
|
||||
it('should return test response', async () => {
|
||||
const actualResponse = await controller.test();
|
||||
|
||||
expect(actualResponse).toHaveProperty('message');
|
||||
expect(actualResponse).toHaveProperty('timestamp');
|
||||
expect(actualResponse.message).toBe('Authentication controller is working');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,90 +1,35 @@
|
||||
import {
|
||||
Body,
|
||||
Req,
|
||||
Controller,
|
||||
HttpCode,
|
||||
Post,
|
||||
UseGuards,
|
||||
Get,
|
||||
ClassSerializerInterceptor,
|
||||
UseInterceptors, SerializeOptions,
|
||||
} from '@nestjs/common';
|
||||
import {AuthenticationService} from './authentication.service';
|
||||
import RegisterDto from './dto/register.dto';
|
||||
import RequestWithUser from './requestWithUser.interface';
|
||||
import {LocalAuthenticationGuard} from './localAuthentication.guard';
|
||||
import JwtAuthenticationGuard from './jwt-authentication.guard';
|
||||
// import {EmailConfirmationService} from '../emailConfirmation/emailConfirmation.service';
|
||||
import {ApiBody} from '@nestjs/swagger';
|
||||
import LogInDto from './dto/logIn.dto';
|
||||
import { UsersService } from 'src/users/user.service';
|
||||
import { Body, Controller, Post, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import { AuthenticationService, LoginResponse } from './authentication.service';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
|
||||
@Controller('authentication')
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@SerializeOptions({
|
||||
strategy: 'excludeAll'
|
||||
})
|
||||
/**
|
||||
* Authentication controller for handling user authentication endpoints
|
||||
*/
|
||||
@Controller('auth')
|
||||
export class AuthenticationController {
|
||||
constructor(
|
||||
private readonly authenticationService: AuthenticationService,
|
||||
private readonly usersService: UsersService,
|
||||
// private readonly emailConfirmationService: EmailConfirmationService,
|
||||
) {
|
||||
}
|
||||
constructor(private readonly authenticationService: AuthenticationService) {}
|
||||
|
||||
@Post('register')
|
||||
async register(@Body() registrationData: RegisterDto) {
|
||||
return this.authenticationService.register(registrationData);
|
||||
}
|
||||
/**
|
||||
* Login endpoint for user authentication
|
||||
* @param loginDto - Login credentials containing email and password
|
||||
* @returns Promise<LoginResponse> - Authentication response
|
||||
*/
|
||||
@Post('login')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async login(@Body() loginDto: LoginDto): Promise<LoginResponse> {
|
||||
return this.authenticationService.login(loginDto);
|
||||
}
|
||||
|
||||
|
||||
@HttpCode(200)
|
||||
@UseGuards(LocalAuthenticationGuard)
|
||||
@Post('log-in')
|
||||
@ApiBody({type: LogInDto})
|
||||
async logIn(@Req() request: RequestWithUser) {
|
||||
const {user} = request;
|
||||
const accessTokenCookie = this.authenticationService.getCookieWithJwtAccessToken(
|
||||
user.id,
|
||||
);
|
||||
const {
|
||||
cookie: refreshTokenCookie,
|
||||
token: refreshToken,
|
||||
} = this.authenticationService.getCookieWithJwtRefreshToken(user.id);
|
||||
|
||||
await this.usersService.setCurrentRefreshToken(refreshToken, user.id);
|
||||
|
||||
request.res.setHeader('Set-Cookie', [
|
||||
accessTokenCookie,
|
||||
refreshTokenCookie,
|
||||
]);
|
||||
|
||||
if (user.isTwoFactorAuthenticationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@UseGuards(JwtAuthenticationGuard)
|
||||
@Post('log-out')
|
||||
@HttpCode(200)
|
||||
async logOut(@Req() request: RequestWithUser) {
|
||||
await this.usersService.removeRefreshToken(request.user.id);
|
||||
request.res.setHeader(
|
||||
'Set-Cookie',
|
||||
this.authenticationService.getCookiesForLogOut(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@UseGuards(JwtAuthenticationGuard)
|
||||
@Get()
|
||||
authenticate(@Req() request: RequestWithUser) {
|
||||
const user = request.user;
|
||||
user.password = undefined;
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* Admin test endpoint for smoke testing
|
||||
* @returns Object - Test response
|
||||
*/
|
||||
@Post('admin/test')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async test(): Promise<{ message: string; timestamp: string }> {
|
||||
return {
|
||||
message: 'Authentication controller is working',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
import {Module} from '@nestjs/common';
|
||||
import {AuthenticationService} from './authentication.service';
|
||||
import {AuthenticationController} from './authentication.controller';
|
||||
import {PassportModule} from '@nestjs/passport';
|
||||
import {LocalStrategy} from './local.strategy';
|
||||
import {ConfigModule, ConfigService} from '@nestjs/config';
|
||||
import {JwtModule} from '@nestjs/jwt';
|
||||
import {JwtStrategy} from "./jwt.strategy";
|
||||
import {UsersModule} from "../users/user.module";
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthenticationController } from './authentication.controller';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
|
||||
/**
|
||||
* Authentication module for handling user authentication
|
||||
*/
|
||||
@Module({
|
||||
imports: [UsersModule, PassportModule,
|
||||
|
||||
ConfigModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
secret: configService.get('JWT_SECRET'),
|
||||
signOptions: {
|
||||
expiresIn: `${configService.get('JWT_EXPIRATION_TIME')}s`,
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
] as const,
|
||||
providers: [AuthenticationService, LocalStrategy, JwtStrategy] as const,
|
||||
controllers: [AuthenticationController] as const,
|
||||
controllers: [AuthenticationController],
|
||||
providers: [AuthenticationService],
|
||||
exports: [AuthenticationService],
|
||||
})
|
||||
export class AuthenticationModule {
|
||||
}
|
||||
export class AuthenticationModule {}
|
||||
|
||||
62
src/authentication/authentication.service.spec.ts
Normal file
62
src/authentication/authentication.service.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
|
||||
describe('AuthenticationService', () => {
|
||||
let service: AuthenticationService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthenticationService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthenticationService>(AuthenticationService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('should return successful login response with valid credentials', async () => {
|
||||
const inputLoginDto: LoginDto = {
|
||||
email: 'test@example.com',
|
||||
password: 'password123',
|
||||
};
|
||||
const expectedResponse = {
|
||||
message: 'Login successful',
|
||||
user: {
|
||||
email: 'test@example.com',
|
||||
},
|
||||
token: 'placeholder-token',
|
||||
};
|
||||
|
||||
const actualResponse = await service.login(inputLoginDto);
|
||||
|
||||
expect(actualResponse).toEqual(expectedResponse);
|
||||
});
|
||||
|
||||
it('should throw UnauthorizedException with invalid email', async () => {
|
||||
const inputLoginDto: LoginDto = {
|
||||
email: 'invalid@example.com',
|
||||
password: 'password123',
|
||||
};
|
||||
|
||||
await expect(service.login(inputLoginDto)).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw UnauthorizedException with invalid password', async () => {
|
||||
const inputLoginDto: LoginDto = {
|
||||
email: 'test@example.com',
|
||||
password: 'wrongpassword',
|
||||
};
|
||||
|
||||
await expect(service.login(inputLoginDto)).rejects.toThrow(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,122 +1,58 @@
|
||||
import {HttpException, HttpStatus, Injectable} from '@nestjs/common';
|
||||
import RegisterDto from './dto/register.dto';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import {JwtService} from '@nestjs/jwt';
|
||||
import {ConfigService} from '@nestjs/config';
|
||||
import {UsersService} from "../users/user.service";
|
||||
import PostgresErrorCode from 'src/database/postgresErrorCodes.enum';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
|
||||
/**
|
||||
* Response interface for successful login
|
||||
*/
|
||||
export interface LoginResponse {
|
||||
message: string;
|
||||
user: {
|
||||
email: string;
|
||||
};
|
||||
token?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication service for handling user authentication
|
||||
*/
|
||||
@Injectable()
|
||||
export class AuthenticationService {
|
||||
constructor(
|
||||
private readonly usersService: UsersService,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
/**
|
||||
* Authenticates a user with email and password
|
||||
* @param loginDto - Login credentials containing email and password
|
||||
* @returns Promise<LoginResponse> - Authentication response
|
||||
* @throws UnauthorizedException - When credentials are invalid
|
||||
*/
|
||||
async login(loginDto: LoginDto): Promise<LoginResponse> {
|
||||
const { email, password } = loginDto;
|
||||
|
||||
// TODO: Implement actual user validation logic
|
||||
// This is a placeholder implementation
|
||||
const isValidUser = await this.validateUser(email, password);
|
||||
|
||||
if (!isValidUser) {
|
||||
throw new UnauthorizedException('Invalid email or password');
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Login successful',
|
||||
user: {
|
||||
email,
|
||||
},
|
||||
// TODO: Generate JWT token
|
||||
token: 'placeholder-token',
|
||||
};
|
||||
}
|
||||
|
||||
public async register(registrationData: RegisterDto) {
|
||||
const hashedPassword = await bcrypt.hash(registrationData.password, 10);
|
||||
try {
|
||||
const createdUser = await this.usersService.create({
|
||||
...registrationData,
|
||||
password: hashedPassword,
|
||||
});
|
||||
createdUser.password = undefined;
|
||||
return createdUser;
|
||||
} catch (error) {
|
||||
if (error?.code === PostgresErrorCode.UniqueViolation) {
|
||||
throw new HttpException(
|
||||
'User with that email already exists',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
console.log(error);
|
||||
throw new HttpException(
|
||||
'Something went wrong',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public getCookieWithJwtAccessToken(
|
||||
userId: number,
|
||||
isSecondFactorAuthenticated = false,
|
||||
) {
|
||||
const payload: TokenPayload = {userId, isSecondFactorAuthenticated};
|
||||
const token = this.jwtService.sign(payload, {
|
||||
secret: this.configService.get('JWT_ACCESS_TOKEN_SECRET'),
|
||||
expiresIn: `${this.configService.get(
|
||||
'JWT_ACCESS_TOKEN_EXPIRATION_TIME',
|
||||
)}s`,
|
||||
});
|
||||
return `Authentication=${token}; HttpOnly; Path=/; Max-Age=${this.configService.get(
|
||||
'JWT_ACCESS_TOKEN_EXPIRATION_TIME',
|
||||
)}`;
|
||||
}
|
||||
|
||||
public getCookieWithJwtRefreshToken(userId: number) {
|
||||
const payload: TokenPayload = {userId};
|
||||
const token = this.jwtService.sign(payload, {
|
||||
secret: this.configService.get('JWT_REFRESH_TOKEN_SECRET'),
|
||||
expiresIn: this.configService.get('JWT_ACCESS_TOKEN_EXPIRATION_TIME'),
|
||||
|
||||
});
|
||||
const cookie = `Refresh=${token}; HttpOnly; Path=/; Max-Age=${this.configService.get(
|
||||
'JWT_REFRESH_TOKEN_EXPIRATION_TIME',
|
||||
)}`;
|
||||
return {
|
||||
cookie,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
public getCookiesForLogOut() {
|
||||
return [
|
||||
'Authentication=; HttpOnly; Path=/; Max-Age=0',
|
||||
'Refresh=; HttpOnly; Path=/; Max-Age=0',
|
||||
];
|
||||
}
|
||||
|
||||
public async getAuthenticatedUser(email: string, plainTextPassword: string) {
|
||||
try {
|
||||
const user = await this.usersService.getByEmail(email);
|
||||
await this.verifyPassword(plainTextPassword, user.password);
|
||||
return user;
|
||||
} catch (error) {
|
||||
throw new HttpException(
|
||||
'Wrong credentials provided',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyPassword(
|
||||
plainTextPassword: string,
|
||||
hashedPassword: string,
|
||||
) {
|
||||
const isPasswordMatching = await bcrypt.compare(
|
||||
plainTextPassword,
|
||||
hashedPassword,
|
||||
);
|
||||
if (!isPasswordMatching) {
|
||||
throw new HttpException(
|
||||
'Wrong credentials provided',
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async getUserFromAuthenticationToken(token: string) {
|
||||
const payload: TokenPayload = this.jwtService.verify(token, {
|
||||
secret: this.configService.get('JWT_ACCESS_TOKEN_SECRET'),
|
||||
});
|
||||
if (payload.userId) {
|
||||
return this.usersService.getById(payload.userId);
|
||||
}
|
||||
}
|
||||
|
||||
public getCookieForLogOut() {
|
||||
return `Authentication=; HttpOnly; Path=/; Max-Age=0`;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validates user credentials
|
||||
* @param email - User email
|
||||
* @param password - User password
|
||||
* @returns Promise<boolean> - Whether credentials are valid
|
||||
*/
|
||||
private async validateUser(email: string, password: string): Promise<boolean> {
|
||||
// TODO: Implement actual user validation against database
|
||||
// For now, using placeholder validation
|
||||
return email === 'test@example.com' && password === 'password123';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { IsEmail, IsString, IsNotEmpty, MinLength } from 'class-validator';
|
||||
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';
|
||||
|
||||
export class LogInDto {
|
||||
@IsEmail()
|
||||
/**
|
||||
* Data Transfer Object for user login
|
||||
*/
|
||||
export class LoginDto {
|
||||
@IsEmail({}, { message: 'Please provide a valid email address' })
|
||||
@IsNotEmpty({ message: 'Email is required' })
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(7)
|
||||
@IsString({ message: 'Password must be a string' })
|
||||
@IsNotEmpty({ message: 'Password is required' })
|
||||
@MinLength(6, { message: 'Password must be at least 6 characters long' })
|
||||
password: string;
|
||||
}
|
||||
|
||||
export default LogInDto;
|
||||
@@ -1,39 +0,0 @@
|
||||
import {
|
||||
IsEmail,
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
MinLength,
|
||||
Matches,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class RegisterDto {
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({
|
||||
deprecated: true,
|
||||
description: 'Use the name property instead',
|
||||
})
|
||||
fullName: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MinLength(7)
|
||||
password: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Has to match a regular expression: /^\\+[1-9]\\d{1,14}$/',
|
||||
example: '+123123123123',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Matches(/^\+[1-9]\d{1,14}$/)
|
||||
phoneNumber: string;
|
||||
}
|
||||
|
||||
export default RegisterDto;
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export default class JwtAuthenticationGuard extends AuthGuard('jwt') {}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Request } from 'express';
|
||||
import { UsersService } from 'src/users/user.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly userService: UsersService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromExtractors([(request: Request): string | null => {
|
||||
return request?.cookies?.Authentication || null;
|
||||
}]),
|
||||
secretOrKey: configService.get('JWT_SECRET')
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: TokenPayload) {
|
||||
return this.userService.getById(payload.userId);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Strategy } from 'passport-local';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthenticationService } from './authentication.service';
|
||||
import User from '../users/entities/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private authenticationService: AuthenticationService) {
|
||||
super({
|
||||
usernameField: 'email'
|
||||
});
|
||||
}
|
||||
async validate(email: string, password: string): Promise<User> {
|
||||
return this.authenticationService.getAuthenticatedUser(email, password);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class LocalAuthenticationGuard extends AuthGuard('local') {}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Request } from 'express';
|
||||
import User from '../users/entities/user.entity';
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export default RequestWithUser;
|
||||
@@ -1,5 +0,0 @@
|
||||
interface TokenPayload {
|
||||
userId: number;
|
||||
isSecondFactorAuthenticated?: boolean;
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
|
||||
import { CategoriesService } from './categories.service';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||
|
||||
@Controller('categories')
|
||||
export class CategoriesController {
|
||||
constructor(private readonly categoriesService: CategoriesService) {}
|
||||
|
||||
@Post()
|
||||
create(@Body() createCategoryDto: CreateCategoryDto) {
|
||||
return this.categoriesService.create(createCategoryDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.categoriesService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.categoriesService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateCategoryDto: UpdateCategoryDto) {
|
||||
return this.categoriesService.update(+id, updateCategoryDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.categoriesService.remove(+id);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import {Module} from '@nestjs/common';
|
||||
import {CategoriesService} from './categories.service';
|
||||
import {CategoriesController} from './categories.controller';
|
||||
import {TypeOrmModule} from "@nestjs/typeorm";
|
||||
import Post from "../posts/entities/post.entity";
|
||||
import Category from "./entities/category.entity";
|
||||
|
||||
@Module({
|
||||
controllers: [CategoriesController],
|
||||
imports: [TypeOrmModule.forFeature([Category])],
|
||||
|
||||
providers: [CategoriesService],
|
||||
exports: [CategoriesService],
|
||||
})
|
||||
export class CategoriesModule {
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
import { UpdateCategoryDto } from './dto/update-category.dto';
|
||||
import {InjectRepository} from "@nestjs/typeorm";
|
||||
import Post from "../posts/entities/post.entity";
|
||||
import {Repository} from "typeorm";
|
||||
import Category from "./entities/category.entity";
|
||||
import {CategoryNotFoundException} from "./exception/categoryNotFound.exception";
|
||||
|
||||
@Injectable()
|
||||
export class CategoriesService {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Category) private repo: Repository<Category>,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
getAllCategories() {
|
||||
return this.repo.find({ relations: ['posts'] });
|
||||
}
|
||||
|
||||
async getCategoryById(id: number) {
|
||||
const category = await this.repo.findOne({
|
||||
where: { id },
|
||||
relations: {
|
||||
posts: true,
|
||||
}
|
||||
});
|
||||
if (category) {
|
||||
return category;
|
||||
}
|
||||
throw new CategoryNotFoundException(id);
|
||||
}
|
||||
|
||||
async updateCategory(id: number, category: UpdateCategoryDto) {
|
||||
await this.repo.update(id, category);
|
||||
const updatedCategory = await this.repo.findOne({
|
||||
where: { id },
|
||||
relations: {
|
||||
posts: true,
|
||||
}
|
||||
});
|
||||
if (updatedCategory) {
|
||||
return updatedCategory
|
||||
}
|
||||
throw new CategoryNotFoundException(id);
|
||||
}
|
||||
|
||||
create(createCategoryDto: CreateCategoryDto) {
|
||||
return 'This action adds a new category';
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all categories`;
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} category`;
|
||||
}
|
||||
|
||||
update(id: number, updateCategoryDto: UpdateCategoryDto) {
|
||||
return `This action updates a #${id} category`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} category`;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export class CreateCategoryDto {}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateCategoryDto } from './create-category.dto';
|
||||
|
||||
export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}
|
||||
@@ -1,16 +0,0 @@
|
||||
import {Column, Entity, ManyToMany, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import Post from "../../posts/entities/post.entity";
|
||||
|
||||
@Entity()
|
||||
class Category {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column()
|
||||
public name: string;
|
||||
|
||||
@ManyToMany(() => Post, (post: Post) => post.categories)
|
||||
public posts: Post[];
|
||||
}
|
||||
|
||||
export default Category;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { NotFoundException } from '@nestjs/common';
|
||||
|
||||
export class CategoryNotFoundException extends NotFoundException {
|
||||
constructor(categoryId: number) {
|
||||
super(`Category with id ${categoryId} not found`);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export interface File {
|
||||
fieldname: string;
|
||||
originalname: string;
|
||||
encoding: string;
|
||||
mimetype: string;
|
||||
size: number;
|
||||
destination: string;
|
||||
filename: string;
|
||||
path: string;
|
||||
buffer: Buffer;
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
export const BUCKET = 'nest-reno';
|
||||
|
||||
|
||||
export const IMAGES = 'images';
|
||||
export const FILES = 'files';
|
||||
export const THUMBNAILS = 'thumbnails';
|
||||
|
||||
export const ACCESSKEY = '004a8f97aa7c9b90000000004';
|
||||
export const SECRETKEY = 'K004lokMgbjY5cfVoFwrzl2gD4AW6yk';
|
||||
|
||||
const aws = require('aws-sdk');
|
||||
|
||||
aws.config.update({
|
||||
accessKeyId: ACCESSKEY,
|
||||
secretAccessKey: SECRETKEY
|
||||
});
|
||||
|
||||
const spacesEndpoint = new aws.Endpoint('s3.us-west-004.backblazeb2.com');
|
||||
export const s3 = new aws.S3({
|
||||
endpoint: spacesEndpoint
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
enum PostgresErrorCode {
|
||||
UniqueViolation = '23505',
|
||||
}
|
||||
export default PostgresErrorCode;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { FilesService } from './files.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import PublicFile from './publicFile.entity';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([PublicFile]), ConfigModule],
|
||||
providers: [FilesService],
|
||||
exports: [FilesService],
|
||||
})
|
||||
export class FilesModule {}
|
||||
@@ -1,78 +0,0 @@
|
||||
import {Injectable} from '@nestjs/common';
|
||||
import {InjectRepository} from '@nestjs/typeorm';
|
||||
import {Repository, QueryRunner} from 'typeorm';
|
||||
import PublicFile from './publicFile.entity';
|
||||
import {S3} from 'aws-sdk';
|
||||
import {ConfigService} from '@nestjs/config';
|
||||
import {v4 as uuid} from 'uuid';
|
||||
import {File} from "../core/file.interface";
|
||||
import {s3} from "../core/s3-config";
|
||||
|
||||
@Injectable()
|
||||
export class FilesService {
|
||||
constructor(
|
||||
@InjectRepository(PublicFile)
|
||||
private publicFilesRepository: Repository<PublicFile>,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
}
|
||||
|
||||
async uploadPublicFile(dataBuffer: Buffer, filename: string) {
|
||||
const uploadResult = await s3.upload({
|
||||
ACL: 'public-read',
|
||||
Bucket: 'nest-reno',
|
||||
Body: dataBuffer,
|
||||
Key: filename,
|
||||
})
|
||||
.promise();
|
||||
|
||||
|
||||
const newFile = this.publicFilesRepository.create({
|
||||
key: uploadResult.Key,
|
||||
url: uploadResult.Location,
|
||||
});
|
||||
await this.publicFilesRepository.save(newFile);
|
||||
return newFile;
|
||||
|
||||
}
|
||||
|
||||
async uploadFile(file: File, folder: String) {
|
||||
const uploadResult = await s3.upload({
|
||||
ACL: 'public-read',
|
||||
Bucket: folder,
|
||||
Body: file.buffer,
|
||||
Key: file.originalname,
|
||||
ContentType: file.mimetype,
|
||||
})
|
||||
.promise();
|
||||
return uploadResult['Location'];
|
||||
}
|
||||
|
||||
async deletePublicFile(fileId: number) {
|
||||
const file = await this.publicFilesRepository.findOneBy({id: fileId});
|
||||
await s3
|
||||
.deleteObject({
|
||||
Bucket: this.configService.get('S3_BUCKET'),
|
||||
Key: file.key,
|
||||
})
|
||||
.promise();
|
||||
await this.publicFilesRepository.delete(fileId);
|
||||
}
|
||||
|
||||
async deletePublicFileWithQueryRunner(
|
||||
fileId: number,
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const file = await queryRunner.manager.findOneBy(PublicFile, {
|
||||
id: fileId,
|
||||
});
|
||||
const s3 = new S3();
|
||||
await s3
|
||||
.deleteObject({
|
||||
Bucket: this.configService.get('S3_BUCKET'),
|
||||
Key: file.key,
|
||||
})
|
||||
.promise();
|
||||
await queryRunner.manager.delete(PublicFile, fileId);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
class PublicFile {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column()
|
||||
public url: string;
|
||||
|
||||
@Column()
|
||||
public key: string;
|
||||
}
|
||||
|
||||
export default PublicFile;
|
||||
21
src/main.ts
21
src/main.ts
@@ -1,21 +1,8 @@
|
||||
import {HttpAdapterHost, NestFactory, Reflector} from '@nestjs/core';
|
||||
import {AppModule} from './app.module';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import {ClassSerializerInterceptor, ValidationPipe} from "@nestjs/common";
|
||||
import {ExceptionsLoggerFilter} from "./utils/exceptionsLogger.filter";
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.use(cookieParser());
|
||||
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
const {httpAdapter} = app.get(HttpAdapterHost);
|
||||
app.useGlobalFilters(new ExceptionsLoggerFilter(httpAdapter));
|
||||
|
||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(
|
||||
app.get(Reflector))
|
||||
);
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export class CreatePostDto {}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreatePostDto } from './create-post.dto';
|
||||
|
||||
export class UpdatePostDto extends PartialType(CreatePostDto) {}
|
||||
@@ -1,29 +0,0 @@
|
||||
import {Column, Entity, ManyToOne, PrimaryGeneratedColumn, ManyToMany, JoinTable} from 'typeorm';
|
||||
import User from "../../users/entities/user.entity";
|
||||
import Category from "../../categories/entities/category.entity";
|
||||
|
||||
@Entity()
|
||||
class Post {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column()
|
||||
public title: string;
|
||||
|
||||
@Column()
|
||||
public content: string;
|
||||
|
||||
@Column({nullable: true})
|
||||
public category?: string;
|
||||
|
||||
@ManyToOne(() => User, (author: User) => author.posts)
|
||||
public author: User;
|
||||
|
||||
|
||||
@ManyToMany(() => Category, (category: Category) => category.posts)
|
||||
@JoinTable()
|
||||
public categories: Category[];
|
||||
|
||||
}
|
||||
|
||||
export default Post;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { NotFoundException } from '@nestjs/common';
|
||||
|
||||
export class PostNotFoundException extends NotFoundException {
|
||||
constructor(postId: number) {
|
||||
super(`Post with id ${postId} not found`);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import {Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Req} from '@nestjs/common';
|
||||
import {PostsService} from './posts.service';
|
||||
import {CreatePostDto} from './dto/create-post.dto';
|
||||
import {UpdatePostDto} from './dto/update-post.dto';
|
||||
import JwtAuthenticationGuard from "../authentication/jwt-authentication.guard";
|
||||
import RequestWithUser from "../authentication/requestWithUser.interface";
|
||||
|
||||
@Controller('posts')
|
||||
export class PostsController {
|
||||
constructor(private readonly postsService: PostsService) {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(JwtAuthenticationGuard)
|
||||
async createPost(@Body() post: CreatePostDto, @Req() req: RequestWithUser) {
|
||||
return this.postsService.createPost(post, req.user);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.postsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.postsService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
|
||||
return this.postsService.update(+id, updatePostDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.postsService.remove(+id);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import {Module} from '@nestjs/common';
|
||||
import {PostsService} from './posts.service';
|
||||
import {PostsController} from './posts.controller';
|
||||
import {TypeOrmModule} from "@nestjs/typeorm";
|
||||
import User from "../users/entities/user.entity";
|
||||
import Post from "./entities/post.entity";
|
||||
|
||||
@Module({
|
||||
controllers: [PostsController],
|
||||
imports: [TypeOrmModule.forFeature([Post])],
|
||||
providers: [PostsService],
|
||||
exports: [PostsService],
|
||||
})
|
||||
export class PostsModule {
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import {Injectable} from '@nestjs/common';
|
||||
import {CreatePostDto} from './dto/create-post.dto';
|
||||
import {UpdatePostDto} from './dto/update-post.dto';
|
||||
import User from "../users/entities/user.entity";
|
||||
import {InjectRepository} from "@nestjs/typeorm";
|
||||
import {Repository} from "typeorm";
|
||||
import Post from './entities/post.entity';
|
||||
import {PostNotFoundException} from "./exception/postNotFound.exception";
|
||||
|
||||
@Injectable()
|
||||
export class PostsService {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Post) private repo: Repository<Post>,
|
||||
) {
|
||||
}
|
||||
|
||||
async createPost(post: CreatePostDto, user: User) {
|
||||
const newPost = this.repo.create({
|
||||
...post,
|
||||
author: user
|
||||
});
|
||||
await this.repo.save(newPost);
|
||||
return newPost;
|
||||
}
|
||||
|
||||
|
||||
getAllPosts() {
|
||||
return this.repo.find({relations: ['author']});
|
||||
}
|
||||
|
||||
async getPostById(id: number) {
|
||||
const post = await this.repo.findOne(
|
||||
{
|
||||
where: {id},
|
||||
relations: {author: true}
|
||||
}
|
||||
);
|
||||
if (post) {
|
||||
return post;
|
||||
}
|
||||
throw new PostNotFoundException(id);
|
||||
}
|
||||
|
||||
async updatePost(id: number, post: UpdatePostDto) {
|
||||
await this.repo.update(id, post);
|
||||
const updatedPost = await this.repo.findOne({
|
||||
|
||||
where: {id},
|
||||
relations: {author: true}
|
||||
|
||||
});
|
||||
if (updatedPost) {
|
||||
return updatedPost
|
||||
}
|
||||
throw new PostNotFoundException(id);
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return `This action returns all posts`;
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
return `This action returns a #${id} post`;
|
||||
}
|
||||
|
||||
update(id: number, updatePostDto: UpdatePostDto) {
|
||||
return `This action updates a #${id} post`;
|
||||
}
|
||||
|
||||
remove(id: number) {
|
||||
return `This action removes a #${id} post`;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export class CreateUserDto {
|
||||
email: string;
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export default CreateUserDto;
|
||||
@@ -1,22 +0,0 @@
|
||||
import {Column, Entity, OneToOne, PrimaryGeneratedColumn} from 'typeorm';
|
||||
import User from "./user.entity";
|
||||
|
||||
@Entity()
|
||||
class Address {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id: number;
|
||||
|
||||
@Column()
|
||||
public street: string;
|
||||
|
||||
@Column()
|
||||
public city: string;
|
||||
|
||||
@Column()
|
||||
public country: string;
|
||||
|
||||
@OneToOne(() => User, (user: User) => user.address)
|
||||
public user: User;
|
||||
}
|
||||
|
||||
export default Address;
|
||||
@@ -1,55 +0,0 @@
|
||||
import {Column, Entity, PrimaryGeneratedColumn, OneToOne, JoinColumn, OneToMany} from 'typeorm';
|
||||
import {Exclude, Expose} from 'class-transformer';
|
||||
import Address from "./address.entity";
|
||||
import Post from "../../posts/entities/post.entity";
|
||||
import PublicFile from "../../files/publicFile.entity";
|
||||
|
||||
@Entity()
|
||||
class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
public id?: number;
|
||||
|
||||
@Column({unique: true})
|
||||
@Expose()
|
||||
public email: string;
|
||||
|
||||
@Column()
|
||||
@Expose()
|
||||
public name: string;
|
||||
|
||||
@Column()
|
||||
public password: string;
|
||||
|
||||
@OneToOne(() => Address, {
|
||||
eager: true,
|
||||
cascade: true
|
||||
})
|
||||
@JoinColumn()
|
||||
public address: Address;
|
||||
|
||||
|
||||
@OneToMany(() => Post, (post: Post) => post.author)
|
||||
public posts: Post[];
|
||||
|
||||
@JoinColumn()
|
||||
@OneToOne(
|
||||
() => PublicFile,
|
||||
{
|
||||
eager: true,
|
||||
nullable: true
|
||||
}
|
||||
)
|
||||
public avatar?: PublicFile;
|
||||
|
||||
@Column({
|
||||
nullable: true,
|
||||
})
|
||||
@Exclude()
|
||||
public currentHashedRefreshToken?: string;
|
||||
|
||||
@Column({default: false})
|
||||
public isTwoFactorAuthenticationEnabled: boolean;
|
||||
|
||||
}
|
||||
|
||||
export default User;
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Controller, Post, Req, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
|
||||
import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard';
|
||||
import RequestWithUser from '../authentication/requestWithUser.interface';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Express } from 'express';
|
||||
import {UsersService} from "./user.service";
|
||||
import 'multer';
|
||||
|
||||
@Controller('user')
|
||||
export class UsersController {
|
||||
constructor(
|
||||
private readonly usersService: UsersService,
|
||||
) {}
|
||||
|
||||
@Post('avatar')
|
||||
@UseGuards(JwtAuthenticationGuard)
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
async addAvatar(@Req() request: RequestWithUser, @UploadedFile() file: Express.Multer.File) {
|
||||
return this.usersService.addAvatar(request.user.id, file.buffer, file.originalname);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import User from './entities/user.entity';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import {UsersService} from "./user.service";
|
||||
import {UsersController} from "./user.controller";
|
||||
import {FilesModule} from "../files/file.module";
|
||||
import Address from "./entities/address.entity";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([User, Address]),
|
||||
ConfigModule,
|
||||
FilesModule,
|
||||
],
|
||||
providers: [UsersService],
|
||||
exports: [UsersService],
|
||||
controllers: [UsersController],
|
||||
})
|
||||
export class UsersModule {}
|
||||
@@ -1,66 +0,0 @@
|
||||
import {HttpException, HttpStatus, Injectable} from '@nestjs/common';
|
||||
import {InjectRepository} from '@nestjs/typeorm';
|
||||
import {Repository} from 'typeorm';
|
||||
import User from './entities/user.entity';
|
||||
import CreateUserDto from './dto/createUser.dto';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import {FilesService} from "../files/files.service";
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>,
|
||||
private readonly filesService: FilesService
|
||||
) {
|
||||
}
|
||||
|
||||
async getByEmail(email: string) {
|
||||
const user = await this.usersRepository.findOne({where: {email}});
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
throw new HttpException(
|
||||
'User with this email does not exist',
|
||||
HttpStatus.NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
async getById(id: number) {
|
||||
const user = await this.usersRepository.findOne({where: {id}});
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
throw new HttpException('User with this id does not exist', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
async addAvatar(userId: number, imageBuffer: Buffer, filename: string) {
|
||||
const avatar = await this.filesService.uploadPublicFile(imageBuffer, filename);
|
||||
const user = await this.getById(userId);
|
||||
await this.usersRepository.update(userId, {
|
||||
...user,
|
||||
avatar
|
||||
});
|
||||
return avatar;
|
||||
}
|
||||
|
||||
async create(userData: CreateUserDto) {
|
||||
const newUser = this.usersRepository.create(userData);
|
||||
await this.usersRepository.save(newUser);
|
||||
return newUser;
|
||||
}
|
||||
|
||||
async setCurrentRefreshToken(refreshToken: string, userId: number) {
|
||||
const currentHashedRefreshToken = await bcrypt.hash(refreshToken, 10);
|
||||
await this.usersRepository.update(userId, {
|
||||
currentHashedRefreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
async removeRefreshToken(userId: number) {
|
||||
return this.usersRepository.update(userId, {
|
||||
currentHashedRefreshToken: null,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Catch, ArgumentsHost } from '@nestjs/common';
|
||||
import { BaseExceptionFilter } from '@nestjs/core';
|
||||
|
||||
@Catch()
|
||||
export class ExceptionsLoggerFilter extends BaseExceptionFilter {
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
console.log('Exception thrown', exception);
|
||||
super.catch(exception, host);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"resolvePackageJsonExports": true,
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
@@ -12,11 +16,10 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"lib": ["dom"] // dom.value is required for the browser
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user