From 967b96184aad939542518d257e040b2021177003 Mon Sep 17 00:00:00 2001 From: radmehr Date: Mon, 1 Jun 2026 14:45:01 +0330 Subject: [PATCH] feat: add admin module --- src/app.module.ts | 2 + .../interceptors/response.interceptor.ts | 53 +++++++++++++++++ src/common/responses/app-response.ts | 6 ++ src/common/responses/index.ts | 2 + src/common/responses/paginated-response.ts | 13 +++++ src/i18n/en/auth.json | 3 +- src/i18n/en/tests.json | 15 +++++ src/i18n/en/validation.json | 4 +- src/i18n/fa/auth.json | 3 +- src/i18n/fa/tests.json | 15 +++++ src/i18n/fa/validation.json | 3 +- src/main.ts | 8 +++ src/modules/admin/admin.module.ts | 9 +++ .../controllers/tests/tests.controller.ts | 21 +++++++ src/modules/admin/http/tests/tests.post.http | 11 ++++ src/modules/auth/constants/index.ts | 1 + .../auth/decorators/roles.decorator.ts | 5 ++ src/modules/auth/guards/global-auth.guard.ts | 36 +++++++++--- .../auth/http/login.post.endpoints.http | 2 +- .../auth/http/refresh.post.endpoints.http | 7 ++- .../auth/http/verify-otp.post.endpoints.http | 4 +- .../auth/providers/generate-token.provider.ts | 3 +- src/modules/auth/providers/login.provider.ts | 9 +-- src/modules/tests/dtos/create-test.dto.ts | 48 ++++++++++++++++ src/modules/tests/entities/test.entity.ts | 1 + src/modules/tests/providers/tests.service.ts | 57 +++++++++++++++++++ src/modules/tests/tests.module.ts | 3 + .../users/http/users.get.endpoints.http | 2 +- .../users/http/users.patch.endpoints.http | 14 ++--- .../users/providers/user-profile.provider.ts | 5 +- 30 files changed, 335 insertions(+), 30 deletions(-) create mode 100644 src/common/interceptors/response.interceptor.ts create mode 100644 src/common/responses/app-response.ts create mode 100644 src/common/responses/index.ts create mode 100644 src/common/responses/paginated-response.ts create mode 100644 src/i18n/en/tests.json create mode 100644 src/i18n/fa/tests.json create mode 100644 src/modules/admin/admin.module.ts create mode 100644 src/modules/admin/controllers/tests/tests.controller.ts create mode 100644 src/modules/admin/http/tests/tests.post.http create mode 100644 src/modules/auth/decorators/roles.decorator.ts create mode 100644 src/modules/tests/dtos/create-test.dto.ts create mode 100644 src/modules/tests/providers/tests.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index d372038..9c60e00 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -20,6 +20,7 @@ import { TestsModule } from './modules/tests/tests.module'; import { QuestionsModule } from './modules/questions/questions.module'; import { SubmissionsModule } from './modules/submissions/submissions.module'; import { ScoringModule } from './modules/scoring/scoring.module'; +import { AdminModule } from './modules/admin/admin.module'; const ENV = process.env.NODE_ENV; @@ -79,6 +80,7 @@ const ENV = process.env.NODE_ENV; QuestionsModule, SubmissionsModule, ScoringModule, + AdminModule, ], providers: [ { diff --git a/src/common/interceptors/response.interceptor.ts b/src/common/interceptors/response.interceptor.ts new file mode 100644 index 0000000..5348fe6 --- /dev/null +++ b/src/common/interceptors/response.interceptor.ts @@ -0,0 +1,53 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { map, Observable } from 'rxjs'; +import { AppResponse, PaginatedResponse } from '../responses'; + +@Injectable() +export class ResponseInterceptor implements NestInterceptor { + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable | Promise> { + return next.handle().pipe( + map((response) => { + if (response instanceof PaginatedResponse) { + return { + success: true, + data: response.data, + pagination: { + ...response.pagination, + totalPages: Math.ceil( + response.pagination.total / response.pagination.limit, + ), + }, + ...(response.message && { + message: response.message, + }), + }; + } + + if (response instanceof AppResponse) { + return { + success: true, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + data: response.data, + ...(response.message && { + message: response.message, + }), + }; + } + + return { + success: true, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + data: response, + }; + }), + ); + } +} diff --git a/src/common/responses/app-response.ts b/src/common/responses/app-response.ts new file mode 100644 index 0000000..33418f4 --- /dev/null +++ b/src/common/responses/app-response.ts @@ -0,0 +1,6 @@ +export class AppResponse { + constructor( + public readonly data: T, + public readonly message?: string, + ) {} +} diff --git a/src/common/responses/index.ts b/src/common/responses/index.ts new file mode 100644 index 0000000..8d4dea1 --- /dev/null +++ b/src/common/responses/index.ts @@ -0,0 +1,2 @@ +export * from './app-response'; +export * from './paginated-response'; diff --git a/src/common/responses/paginated-response.ts b/src/common/responses/paginated-response.ts new file mode 100644 index 0000000..946f81a --- /dev/null +++ b/src/common/responses/paginated-response.ts @@ -0,0 +1,13 @@ +export interface PaginationMeta { + page: number; + limit: number; + total: number; +} + +export class PaginatedResponse { + constructor( + public readonly data: T[], + public readonly pagination: PaginationMeta, + public readonly message?: string, + ) {} +} diff --git a/src/i18n/en/auth.json b/src/i18n/en/auth.json index 86f4ebc..e6ac93a 100644 --- a/src/i18n/en/auth.json +++ b/src/i18n/en/auth.json @@ -8,7 +8,8 @@ "wrongPhoneOrPass": "Entered phone number or password is wrong.", "unauthorized": "Unauthorized access", "tokenReuse": "Token reuse detected.", - "invalidOTP": "OTP is invalid or expired." + "invalidOTP": "OTP is invalid or expired.", + "accessDenied": "Access denied. You do not have the required permissions to perform this action." }, "messages": { "otpSent": "OTP sent to {phone}.", diff --git a/src/i18n/en/tests.json b/src/i18n/en/tests.json new file mode 100644 index 0000000..d607be9 --- /dev/null +++ b/src/i18n/en/tests.json @@ -0,0 +1,15 @@ +{ + "fields": { + "title": "Title", + "description": "Description", + "accessType": "Access Type", + "price": "Price", + "isActive": "Is Active" + }, + "errors": { + "intPrice": "Price should be an integer." + }, + "messages": { + "createSuccess": "Test Created Successfully." + } +} diff --git a/src/i18n/en/validation.json b/src/i18n/en/validation.json index 9542d93..e502f0f 100644 --- a/src/i18n/en/validation.json +++ b/src/i18n/en/validation.json @@ -3,5 +3,7 @@ "minLengthField": "{field} should contain atleast {min} characters.", "maxLengthField": "{field} should contain maximum {max} characters.", "wrongField": "{field} is not correct.", - "fieldsMustMatch": "{field1} must match {field2}" + "wrongFieldFormat": "{field} format is wrong.", + "fieldsMustMatch": "{field1} must match {field2}.", + "duplicateField": "{field} already exists." } diff --git a/src/i18n/fa/auth.json b/src/i18n/fa/auth.json index 5f84bc2..983cdec 100644 --- a/src/i18n/fa/auth.json +++ b/src/i18n/fa/auth.json @@ -8,7 +8,8 @@ "wrongPhoneOrPass": "شماره موبایل یا رمز عبور اشتباه است.", "unauthorized": "دسترسی غیر مجاز", "tokenReuse": "توکن قبلا استفاده شده.", - "invalidOTP": "کد تایید اشتباه است یا منقضی شده." + "invalidOTP": "کد تایید اشتباه است یا منقضی شده.", + "accessDenied": "دسترسی غیرمجاز. شما مجوزهای لازم برای انجام این عملیات را ندارید." }, "messages": { "otpSent": "کد تایید به شماره {phone} ارسال شد.", diff --git a/src/i18n/fa/tests.json b/src/i18n/fa/tests.json new file mode 100644 index 0000000..2783ba1 --- /dev/null +++ b/src/i18n/fa/tests.json @@ -0,0 +1,15 @@ +{ + "fields": { + "title": "عنوان", + "description": "توضیحات", + "accessType": "نوع دسترسی", + "price": "قیمت", + "isActive": "فعال بودن" + }, + "errors": { + "intPrice": "قیمت باید عدد صحیح باشد." + }, + "messages": { + "createSuccess": "تست با موفقیت ایجاد شد." + } +} diff --git a/src/i18n/fa/validation.json b/src/i18n/fa/validation.json index 4588e13..f2cb936 100644 --- a/src/i18n/fa/validation.json +++ b/src/i18n/fa/validation.json @@ -4,5 +4,6 @@ "maxLengthField": "{field} باید حداکثر شامل {max} کاراکتر باشد.", "wrongField": "{field} معتبر نیست.", "wrongFieldFormat": "فرمت {field} معتبر نیست.", - "fieldsMustMatch": "مقادیر {field1} و {field2} باید با هم برابر باشند." + "fieldsMustMatch": "مقادیر {field1} و {field2} باید با هم برابر باشند.", + "duplicateField": "مقدار {field} تکراری است." } diff --git a/src/main.ts b/src/main.ts index a5dc91a..b87fa41 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { I18nValidationExceptionFilter, I18nValidationPipe } from 'nestjs-i18n'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import { ResponseInterceptor } from './common/interceptors/response.interceptor'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -28,6 +29,13 @@ async function bootstrap() { }), ); + app.useGlobalInterceptors(new ResponseInterceptor()); + + app.enableCors({ + origin: ['http://localhost:5173'], + credentials: true, + }); + const config = new DocumentBuilder() .setTitle('Borna API') .setDescription('Borna API Documentation') diff --git a/src/modules/admin/admin.module.ts b/src/modules/admin/admin.module.ts new file mode 100644 index 0000000..3eda9af --- /dev/null +++ b/src/modules/admin/admin.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { TestsController } from './controllers/tests/tests.controller'; +import { TestsModule } from '../tests/tests.module'; + +@Module({ + imports: [TestsModule], + controllers: [TestsController], +}) +export class AdminModule {} diff --git a/src/modules/admin/controllers/tests/tests.controller.ts b/src/modules/admin/controllers/tests/tests.controller.ts new file mode 100644 index 0000000..eb834b2 --- /dev/null +++ b/src/modules/admin/controllers/tests/tests.controller.ts @@ -0,0 +1,21 @@ +import { Role } from '@/common/enums/roles.enum'; +import { Roles } from '@/modules/auth/decorators/roles.decorator'; +import { CreateTestDTO } from '@/modules/tests/dtos/create-test.dto'; +import { TestsService } from '@/modules/tests/providers/tests.service'; +import { Body, Controller, Post } from '@nestjs/common'; + +@Controller('admin/tests') +export class TestsController { + constructor( + /** + * Inject Tests Service + */ + private readonly testsService: TestsService, + ) {} + + @Post() + @Roles(Role.SUPERUSER, Role.ADMIN) + public async createTest(@Body() createTestDto: CreateTestDTO) { + return await this.testsService.createOne(createTestDto); + } +} diff --git a/src/modules/admin/http/tests/tests.post.http b/src/modules/admin/http/tests/tests.post.http new file mode 100644 index 0000000..e275ae6 --- /dev/null +++ b/src/modules/admin/http/tests/tests.post.http @@ -0,0 +1,11 @@ +POST http://localhost:3000/admin/tests/ +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwMjIzNDExLCJleHAiOjE3ODAyMjcwMTEsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.FNTIrk6FKO8P88yKnRCybZ6E5N3KlJaCHqZt5DMVgds + +{ + "title": "Test Three", + "description": "Description Three", + "accessType": "free", + "price": "1234000", + "isActive": true +} \ No newline at end of file diff --git a/src/modules/auth/constants/index.ts b/src/modules/auth/constants/index.ts index 9c16e1d..e70c029 100644 --- a/src/modules/auth/constants/index.ts +++ b/src/modules/auth/constants/index.ts @@ -1 +1,2 @@ export const IS_PUBLIC_KEY = 'isPublic'; +export const ROLES_KEY = 'roles'; diff --git a/src/modules/auth/decorators/roles.decorator.ts b/src/modules/auth/decorators/roles.decorator.ts new file mode 100644 index 0000000..e12b197 --- /dev/null +++ b/src/modules/auth/decorators/roles.decorator.ts @@ -0,0 +1,5 @@ +import { Role } from '@/common/enums/roles.enum'; +import { SetMetadata } from '@nestjs/common'; +import { ROLES_KEY } from '../constants'; + +export const Roles = (...roles: Array) => SetMetadata(ROLES_KEY, roles); diff --git a/src/modules/auth/guards/global-auth.guard.ts b/src/modules/auth/guards/global-auth.guard.ts index 3f3b139..262fa2f 100644 --- a/src/modules/auth/guards/global-auth.guard.ts +++ b/src/modules/auth/guards/global-auth.guard.ts @@ -1,8 +1,14 @@ -import { IS_PUBLIC_KEY } from '../constants'; +import { Role } from '@/common/enums/roles.enum'; +import { IS_PUBLIC_KEY, ROLES_KEY } from '../constants'; import { JwtAuthGuard } from './jwt-auth.guard'; -import { ExecutionContext, Injectable } from '@nestjs/common'; +import { + ExecutionContext, + ForbiddenException, + Injectable, +} from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { Observable } from 'rxjs'; +import type { Request } from 'express'; +import { User } from '@/modules/users/entities/user.entity'; @Injectable() export class GlobalAuthGuard extends JwtAuthGuard { @@ -15,9 +21,7 @@ export class GlobalAuthGuard extends JwtAuthGuard { super(); } - canActivate( - context: ExecutionContext, - ): boolean | Promise | Observable { + async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), @@ -27,6 +31,24 @@ export class GlobalAuthGuard extends JwtAuthGuard { return true; } - return super.canActivate(context); + await super.canActivate(context); + + const requiredRoles = this.reflector.getAllAndOverride>( + ROLES_KEY, + [context.getHandler(), context.getClass()], + ); + + if (requiredRoles && requiredRoles.length > 0) { + const request = context.switchToHttp().getRequest(); + const user = request.user as User; + + const hasRole = requiredRoles.includes(user.role); + + if (!hasRole) { + throw new ForbiddenException('auth.errors.accessDenied'); + } + } + + return true; } } diff --git a/src/modules/auth/http/login.post.endpoints.http b/src/modules/auth/http/login.post.endpoints.http index 6f9cf1a..a2ae2aa 100644 --- a/src/modules/auth/http/login.post.endpoints.http +++ b/src/modules/auth/http/login.post.endpoints.http @@ -4,5 +4,5 @@ lang: fa { "phone": "09333026363", - "password": "rad1.3.5" + "password": "Password1@" } \ No newline at end of file diff --git a/src/modules/auth/http/refresh.post.endpoints.http b/src/modules/auth/http/refresh.post.endpoints.http index 0a2aa8f..3c882fd 100644 --- a/src/modules/auth/http/refresh.post.endpoints.http +++ b/src/modules/auth/http/refresh.post.endpoints.http @@ -1,6 +1,11 @@ POST http://localhost:3000/auth/refresh Content-Type: application/json +// { +// "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwMjIzNDExLCJleHAiOjE3ODAyMjcwMTEsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.FNTIrk6FKO8P88yKnRCybZ6E5N3KlJaCHqZt5DMVgds", +// "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJqdGkiOiJmZDRjZGViYS1iMjUzLTQ5MjgtOGIxYS0yZTJhZDMwOTIzYzMiLCJpYXQiOjE3ODAyMjM0MTEsImV4cCI6MTc4MTA4NzQxMSwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.EO0OpjQix794Y7yVZ8pjLH5HngYfCTwu3lonEfXtLIQ" +// } + { - "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjksImp0aSI6IjljOTY5OWRhLWI0NTgtNDc0Ny04YTRkLTQyODQxMDllOTVkZCIsImlhdCI6MTc3ODMwNDMyNSwiZXhwIjoxNzc5MTY4MzI1LCJhdWQiOiJsb2NhbGhvc3QiLCJpc3MiOiJsb2NhbGhvc3QifQ.oiHuj3vJRQl7rG8H0fFEfU9cG91KrLGdXqNAYFGTXVA" + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJqdGkiOiIyZjQ1YzcxMi01ZGQzLTRhY2ItYjExYS04ZDA5YTc3YTVmYTciLCJpYXQiOjE3Nzk5NTY1OTksImV4cCI6MTc4MDgyMDU5OSwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.GX4kSCUCIvBVq9l4lQ8dkETvFZ1NwBIm9NGOIjBsQCc" } diff --git a/src/modules/auth/http/verify-otp.post.endpoints.http b/src/modules/auth/http/verify-otp.post.endpoints.http index 3d426ee..6493b47 100644 --- a/src/modules/auth/http/verify-otp.post.endpoints.http +++ b/src/modules/auth/http/verify-otp.post.endpoints.http @@ -2,6 +2,6 @@ POST http://localhost:3000/auth/verify-otp Content-Type: application/json { - "phone": "09121111114", - "otp": "99696" + "phone": "09333026363", + "otp": "44875" } \ No newline at end of file diff --git a/src/modules/auth/providers/generate-token.provider.ts b/src/modules/auth/providers/generate-token.provider.ts index 5b797c8..c405022 100644 --- a/src/modules/auth/providers/generate-token.provider.ts +++ b/src/modules/auth/providers/generate-token.provider.ts @@ -8,6 +8,7 @@ import { RefreshTokenPayload, } from '../interfaces/jwt.interface'; import { AuthRedisProvider } from './auth-redis.provider'; +import { AppResponse } from '@/common/responses'; @Injectable() export class GenerateTokenProvider { @@ -75,6 +76,6 @@ export class GenerateTokenProvider { ), ]); - return { access, refresh }; + return new AppResponse({ access, refresh }); } } diff --git a/src/modules/auth/providers/login.provider.ts b/src/modules/auth/providers/login.provider.ts index ec22406..187b023 100644 --- a/src/modules/auth/providers/login.provider.ts +++ b/src/modules/auth/providers/login.provider.ts @@ -6,6 +6,7 @@ import { HashingProvider } from '@/common/modules/hashing/providers/hashing.prov import { GenerateTokenProvider } from './generate-token.provider'; import { randomUUID } from 'crypto'; import { I18nService } from 'nestjs-i18n'; +import { AppResponse } from '@/common/responses'; @Injectable() export class LoginProvider { @@ -57,10 +58,10 @@ export class LoginProvider { } if (!password) { - return { - newUser: false, - message: this.i18nService.translate('auth.messages.sendPass'), - }; + return new AppResponse( + { newUser: false }, + this.i18nService.translate('auth.messages.sendPass'), + ); } const isPasswordCorrect = await this.hashingProvider.compare( diff --git a/src/modules/tests/dtos/create-test.dto.ts b/src/modules/tests/dtos/create-test.dto.ts new file mode 100644 index 0000000..b024957 --- /dev/null +++ b/src/modules/tests/dtos/create-test.dto.ts @@ -0,0 +1,48 @@ +import { + IsBoolean, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, + Matches, + MaxLength, +} from 'class-validator'; +import { i18nValidationMessage as t } from 'nestjs-i18n'; +import { AccessType } from '../enums/access-types.enum'; + +export class CreateTestDTO { + @IsString() + @IsNotEmpty({ + message: t('validation.requiredField', { + field: '$t(tests.fields.title)', + }), + }) + @MaxLength(255, { + message: t('validation.maxLengthField', { + field: '$t(tests.fields.title)', + max: 255, + }), + }) + title: string; + + @IsString() + @IsNotEmpty({ + message: t('validation.requiredField', { + field: '$t(tests.fields.description)', + }), + }) + description: string; + + @IsEnum(AccessType) + @IsOptional() + accessType?: AccessType; + + @IsString() + @IsOptional() + @Matches(/^\d+$/, { message: t('tests.errors.intPrice') }) + price?: string; + + @IsBoolean() + @IsOptional() + isActive?: boolean; +} diff --git a/src/modules/tests/entities/test.entity.ts b/src/modules/tests/entities/test.entity.ts index bb67e92..dcef869 100644 --- a/src/modules/tests/entities/test.entity.ts +++ b/src/modules/tests/entities/test.entity.ts @@ -20,6 +20,7 @@ export class Test { type: 'varchar', length: 255, nullable: false, + unique: true, }) title: string; diff --git a/src/modules/tests/providers/tests.service.ts b/src/modules/tests/providers/tests.service.ts new file mode 100644 index 0000000..38e8ce3 --- /dev/null +++ b/src/modules/tests/providers/tests.service.ts @@ -0,0 +1,57 @@ +import { + HttpStatus, + Injectable, + RequestTimeoutException, +} from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { Test } from '../entities/test.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CreateTestDTO } from '../dtos/create-test.dto'; +import { AppException } from '@/common/exceptions/app.exception'; +import { AppResponse } from '@/common/responses'; +import { I18nService } from 'nestjs-i18n'; + +@Injectable() +export class TestsService { + constructor( + /** + * Inject Tests Repository + */ + @InjectRepository(Test) + private readonly testRepository: Repository, + + /** + * Inject I18n Service + */ + private readonly i18nService: I18nService, + ) {} + + public async createOne(createTestDto: CreateTestDTO) { + const titleExists = await this.testRepository.findOne({ + where: { + title: createTestDto.title, + }, + }); + + if (titleExists) { + throw new AppException('validation.duplicateField', HttpStatus.CONFLICT, { + field: '$t(tests.fields.title)', + }); + } + + const newTest = this.testRepository.create(createTestDto); + + try { + await this.testRepository.save(newTest); + return new AppResponse( + newTest, + this.i18nService.translate('tests.messages.createSuccess'), + ); + } catch (err) { + throw new RequestTimeoutException('common.errors.requestTimeout', { + cause: err, + description: 'Error connecting to the database', + }); + } + } +} diff --git a/src/modules/tests/tests.module.ts b/src/modules/tests/tests.module.ts index 84e9fe1..881eff5 100644 --- a/src/modules/tests/tests.module.ts +++ b/src/modules/tests/tests.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Test } from './entities/test.entity'; +import { TestsService } from './providers/tests.service'; @Module({ imports: [TypeOrmModule.forFeature([Test])], + providers: [TestsService], + exports: [TestsService], }) export class TestsModule {} diff --git a/src/modules/users/http/users.get.endpoints.http b/src/modules/users/http/users.get.endpoints.http index 0ddb983..706a2e1 100644 --- a/src/modules/users/http/users.get.endpoints.http +++ b/src/modules/users/http/users.get.endpoints.http @@ -1,2 +1,2 @@ GET http://localhost:3000/users/profile -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsInBob25lIjoiMDkzMzMwMjYzNjMiLCJpYXQiOjE3NzgzOTE0MTQsImV4cCI6MTc3ODM5NTAxNCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.ckp0DoNxarVkraiel02daRttbmESuN9vKQe3JNjBL4c \ No newline at end of file +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzgwMjIzNDExLCJleHAiOjE3ODAyMjcwMTEsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.FNTIrk6FKO8P88yKnRCybZ6E5N3KlJaCHqZt5DMVgds diff --git a/src/modules/users/http/users.patch.endpoints.http b/src/modules/users/http/users.patch.endpoints.http index 0246eae..f6f36f0 100644 --- a/src/modules/users/http/users.patch.endpoints.http +++ b/src/modules/users/http/users.patch.endpoints.http @@ -1,16 +1,16 @@ PATCH http://localhost:3000/users/profile Content-Type: application/json -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsInBob25lIjoiMDkzMzMwMjYzNjMiLCJpYXQiOjE3NzgzOTE0MTQsImV4cCI6MTc3ODM5NTAxNCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.ckp0DoNxarVkraiel02daRttbmESuN9vKQe3JNjBL4c +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzc5OTIzMzU4LCJleHAiOjE3Nzk5MjY5NTgsImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.1XFNvOFdfSc2LRybiCNlRPAdP5JNWpCu5MeJCZIE9kc lang: fa // { -// "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsInBob25lIjoiMDkzMzMwMjYzNjMiLCJpYXQiOjE3NzgzOTE0MTQsImV4cCI6MTc3ODM5NTAxNCwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.ckp0DoNxarVkraiel02daRttbmESuN9vKQe3JNjBL4c", -// "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjIsImp0aSI6IjI3ZTk1NGI2LWRjZDktNDVkNy04OWRhLTIwYWM0NjcwZmZmNCIsImlhdCI6MTc3ODM5MTQxNCwiZXhwIjoxNzc5MjU1NDE0LCJhdWQiOiJsb2NhbGhvc3QiLCJpc3MiOiJsb2NhbGhvc3QifQ.mX3KideTda658EEi03cQGxgi4j6qwPeoCg2wr9dj_u0" +// "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJwaG9uZSI6IjA5MzMzMDI2MzYzIiwiaWF0IjoxNzc5OTU2NTk5LCJleHAiOjE3Nzk5NjAxOTksImF1ZCI6ImxvY2FsaG9zdCIsImlzcyI6ImxvY2FsaG9zdCJ9.e1Qay8cf6jW4OYPY8HCLs5TWUHObICkoAidj0QBhbM4", +// "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEwLCJqdGkiOiIyZjQ1YzcxMi01ZGQzLTRhY2ItYjExYS04ZDA5YTc3YTVmYTciLCJpYXQiOjE3Nzk5NTY1OTksImV4cCI6MTc4MDgyMDU5OSwiYXVkIjoibG9jYWxob3N0IiwiaXNzIjoibG9jYWxob3N0In0.GX4kSCUCIvBVq9l4lQ8dkETvFZ1NwBIm9NGOIjBsQCc" // } { - "firstName": "Radi", - "lastName": "Tar" - // "password": "Password1@", - // "confirmPassword": "Password1@" + "firstName": "Radmehr", + "lastName": "Tarnas", + "password": "Password1@", + "confirmPassword": "Password1@" } \ No newline at end of file diff --git a/src/modules/users/providers/user-profile.provider.ts b/src/modules/users/providers/user-profile.provider.ts index cbc8a95..8683bf0 100644 --- a/src/modules/users/providers/user-profile.provider.ts +++ b/src/modules/users/providers/user-profile.provider.ts @@ -5,6 +5,7 @@ import { User } from '../entities/user.entity'; import { UpdateProfileDTO } from '../dto/update-profile.dto'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import { AppResponse } from '@/common/responses'; @Injectable() export class UserProfileProvider { @@ -27,12 +28,12 @@ export class UserProfileProvider { ) {} public getProfile(user: User) { - return { + return new AppResponse({ phone: user.phone, firstName: user.firstName, lastName: user.lastName, dateJoined: user.createdAt, - }; + }); } public async updateProfile(user: User, updateProfileDto: UpdateProfileDTO) {