@anmol0493/fullstack-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/backend-express/.env.example +2 -0
  4. package/backend-express/index.js +2 -0
  5. package/backend-express/package-lock.json +1939 -0
  6. package/backend-express/package.json +25 -0
  7. package/backend-express/src/config/db.js +20 -0
  8. package/backend-express/src/controller/auth.js +71 -0
  9. package/backend-express/src/middleware/auth.js +36 -0
  10. package/backend-express/src/middleware/validator.js +16 -0
  11. package/backend-express/src/models/user.js +26 -0
  12. package/backend-express/src/routes/auth.js +25 -0
  13. package/backend-express/src/server.js +28 -0
  14. package/backend-express/src/utils/constants.js +14 -0
  15. package/backend-express/src/utils/helper.js +30 -0
  16. package/backend-express/src/utils/schema/auth.js +34 -0
  17. package/backend-nestjs/.env.example +3 -0
  18. package/backend-nestjs/.prettierrc +4 -0
  19. package/backend-nestjs/README.md +99 -0
  20. package/backend-nestjs/eslint.config.mjs +35 -0
  21. package/backend-nestjs/nest-cli.json +8 -0
  22. package/backend-nestjs/package.json +99 -0
  23. package/backend-nestjs/pnpm-lock.yaml +7848 -0
  24. package/backend-nestjs/src/app.controller.ts +12 -0
  25. package/backend-nestjs/src/app.module.ts +13 -0
  26. package/backend-nestjs/src/app.service.ts +8 -0
  27. package/backend-nestjs/src/common/decorators/user.decorator.ts +8 -0
  28. package/backend-nestjs/src/common/dtos/common.dto.ts +87 -0
  29. package/backend-nestjs/src/common/enum/index.ts +0 -0
  30. package/backend-nestjs/src/common/exceptions/custom.exception.ts +28 -0
  31. package/backend-nestjs/src/common/filters/http-exception.filter.ts +46 -0
  32. package/backend-nestjs/src/common/guard/permission.guard.ts +18 -0
  33. package/backend-nestjs/src/common/middleware/auth.middleware.ts +20 -0
  34. package/backend-nestjs/src/common/pipes/validation.pipe.ts +61 -0
  35. package/backend-nestjs/src/common/utils/constants.ts +36 -0
  36. package/backend-nestjs/src/common/utils/helper.ts +43 -0
  37. package/backend-nestjs/src/core/core.module.ts +8 -0
  38. package/backend-nestjs/src/core/jwt/jwt.module.ts +10 -0
  39. package/backend-nestjs/src/core/jwt/jwt.service.ts +45 -0
  40. package/backend-nestjs/src/core/prisma/prisma.module.ts +9 -0
  41. package/backend-nestjs/src/core/prisma/prisma.service.ts +17 -0
  42. package/backend-nestjs/src/core/prisma/schema.prisma +27 -0
  43. package/backend-nestjs/src/main.ts +26 -0
  44. package/backend-nestjs/src/module/auth/auth.controller.ts +30 -0
  45. package/backend-nestjs/src/module/auth/auth.module.ts +10 -0
  46. package/backend-nestjs/src/module/auth/auth.service.ts +83 -0
  47. package/backend-nestjs/src/module/auth/dto/index.ts +28 -0
  48. package/backend-nestjs/src/module/index.module.ts +17 -0
  49. package/backend-nestjs/src/scripts/migrate.js +40 -0
  50. package/backend-nestjs/tsconfig.build.json +4 -0
  51. package/backend-nestjs/tsconfig.json +22 -0
  52. package/frontend/.env.example +1 -0
  53. package/frontend/README.md +54 -0
  54. package/frontend/eslint.config.js +28 -0
  55. package/frontend/index.html +13 -0
  56. package/frontend/package-lock.json +3813 -0
  57. package/frontend/package.json +43 -0
  58. package/frontend/public/vite.svg +1 -0
  59. package/frontend/src/App.tsx +24 -0
  60. package/frontend/src/assets/react.svg +1 -0
  61. package/frontend/src/components/Layout.tsx +59 -0
  62. package/frontend/src/components/ui/AlertDialog.tsx +47 -0
  63. package/frontend/src/components/ui/Button.tsx +61 -0
  64. package/frontend/src/components/ui/CommonAlertDialog.tsx +57 -0
  65. package/frontend/src/components/ui/FormInput.tsx +73 -0
  66. package/frontend/src/components/ui/Loader.tsx +7 -0
  67. package/frontend/src/hook/useFetchUser.ts +38 -0
  68. package/frontend/src/index.css +1 -0
  69. package/frontend/src/lib/constants.ts +24 -0
  70. package/frontend/src/lib/schema.ts +12 -0
  71. package/frontend/src/lib/utils.ts +71 -0
  72. package/frontend/src/main.tsx +11 -0
  73. package/frontend/src/pages/Home.tsx +5 -0
  74. package/frontend/src/pages/Login.tsx +67 -0
  75. package/frontend/src/pages/Signup.tsx +67 -0
  76. package/frontend/src/redux/api/auth.ts +19 -0
  77. package/frontend/src/redux/slice/auth.ts +39 -0
  78. package/frontend/src/redux/store.ts +30 -0
  79. package/frontend/src/routes/index.tsx +20 -0
  80. package/frontend/src/routes/middleware.ts +18 -0
  81. package/frontend/src/types/index.ts +12 -0
  82. package/frontend/src/vite-env.d.ts +1 -0
  83. package/frontend/tsconfig.app.json +26 -0
  84. package/frontend/tsconfig.json +7 -0
  85. package/frontend/tsconfig.node.json +24 -0
  86. package/frontend/vite.config.ts +19 -0
  87. package/package.json +34 -0
  88. package/scripts/setup.js +73 -0
@@ -0,0 +1,12 @@
1
+ import { Controller, Get } from '@nestjs/common';
2
+ import { AppService } from './app.service';
3
+
4
+ @Controller()
5
+ export class AppController {
6
+ constructor(private readonly appService: AppService) {}
7
+
8
+ @Get()
9
+ getHello(): string {
10
+ return this.appService.getHello();
11
+ }
12
+ }
@@ -0,0 +1,13 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { AppController } from './app.controller';
3
+ import { AppService } from './app.service';
4
+ import { ConfigModule } from '@nestjs/config';
5
+ import { CoreModule } from './core/core.module';
6
+ import { IndexModule } from './module/index.module';
7
+
8
+ @Module({
9
+ imports: [ConfigModule.forRoot({ isGlobal: true }), CoreModule, IndexModule],
10
+ controllers: [AppController],
11
+ providers: [AppService]
12
+ })
13
+ export class AppModule {}
@@ -0,0 +1,8 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ @Injectable()
4
+ export class AppService {
5
+ getHello(): string {
6
+ return 'Hello World!';
7
+ }
8
+ }
@@ -0,0 +1,8 @@
1
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
+
3
+ export const CurrentUser = createParamDecorator(
4
+ (data: unknown, ctx: ExecutionContext) => {
5
+ const { user } = ctx.switchToHttp().getRequest();
6
+ return user;
7
+ }
8
+ );
@@ -0,0 +1,87 @@
1
+ import { Transform, Type } from 'class-transformer';
2
+ import {
3
+ IsBoolean,
4
+ IsInt,
5
+ IsOptional,
6
+ IsString,
7
+ ValidateNested
8
+ } from 'class-validator';
9
+
10
+ export class ResponseDto {
11
+ @IsInt()
12
+ status_code: number;
13
+
14
+ @IsBoolean()
15
+ error: boolean;
16
+
17
+ @IsString()
18
+ message: string;
19
+
20
+ constructor(response: {
21
+ status_code: number;
22
+ error: boolean;
23
+ message: string;
24
+ }) {
25
+ this.status_code = response.status_code;
26
+ this.error = response.error;
27
+ this.message = response.message;
28
+ }
29
+ }
30
+
31
+ export class ResponseBaseDto<T> extends ResponseDto {
32
+ @Type(() => Object)
33
+ result: T;
34
+
35
+ constructor(response: {
36
+ status_code: number;
37
+ error: boolean;
38
+ message: string;
39
+ result: T;
40
+ }) {
41
+ super({
42
+ status_code: response.status_code,
43
+ error: response.error,
44
+ message: response.message
45
+ });
46
+ this.result = response.result;
47
+ }
48
+ }
49
+
50
+ export class ResponseBaseWithCountDto<T> extends ResponseDto {
51
+ @ValidateNested({ each: true })
52
+ @Type(() => Object)
53
+ result: {
54
+ data: T[];
55
+ count: number;
56
+ };
57
+
58
+ constructor(response: {
59
+ status_code: number;
60
+ error: boolean;
61
+ message: string;
62
+ result: {
63
+ data: T[];
64
+ count: number;
65
+ };
66
+ }) {
67
+ super({
68
+ status_code: response.status_code,
69
+ error: response.error,
70
+ message: response.message
71
+ });
72
+ this.result = response.result;
73
+ }
74
+ }
75
+
76
+ export class RequestQueryDto {
77
+ @IsOptional()
78
+ @Transform(({ value }) => (value ? Number(value) : undefined))
79
+ page?: number;
80
+
81
+ @IsOptional()
82
+ @Transform(({ value }) => (value ? Number(value) : undefined))
83
+ limit?: number;
84
+
85
+ @IsOptional()
86
+ search?: string;
87
+ }
File without changes
@@ -0,0 +1,28 @@
1
+ import { HttpException, HttpStatus } from '@nestjs/common';
2
+ import { ERROR_MSG } from 'src/common/utils/constants';
3
+
4
+ export class ResourceNotFound extends HttpException {
5
+ constructor(resourceName: string, resourceId?: string) {
6
+ super(
7
+ resourceId
8
+ ? `${resourceName} with ${resourceId} not found`
9
+ : `${resourceName} not found`,
10
+ HttpStatus.NOT_FOUND
11
+ );
12
+ }
13
+ }
14
+
15
+ export class Unauthorized extends HttpException {
16
+ constructor() {
17
+ super(ERROR_MSG.UNAUTHORIZED, HttpStatus.UNAUTHORIZED);
18
+ }
19
+ }
20
+
21
+ export class CustomException extends HttpException {
22
+ constructor(
23
+ message: string = ERROR_MSG.INTERNAL_SERVER_ERROR,
24
+ status_code: HttpStatus = HttpStatus.BAD_REQUEST
25
+ ) {
26
+ super(message, status_code);
27
+ }
28
+ }
@@ -0,0 +1,46 @@
1
+ import {
2
+ ExceptionFilter,
3
+ Catch,
4
+ ArgumentsHost,
5
+ HttpException,
6
+ HttpStatus
7
+ } from '@nestjs/common';
8
+ import { Response } from 'express';
9
+ import { ERROR_MSG } from 'src/common/utils/constants';
10
+
11
+ @Catch()
12
+ export class HttpExceptionFilter implements ExceptionFilter {
13
+ catch(exception: unknown, host: ArgumentsHost) {
14
+ const ctx = host.switchToHttp();
15
+ const response = ctx.getResponse<Response>();
16
+ const request = ctx.getRequest<Request>();
17
+
18
+ const status =
19
+ exception instanceof HttpException
20
+ ? exception.getStatus()
21
+ : HttpStatus.INTERNAL_SERVER_ERROR;
22
+ const message =
23
+ exception instanceof HttpException
24
+ ? exception.getResponse()
25
+ : ERROR_MSG.INTERNAL_SERVER_ERROR;
26
+
27
+ const errorResponse: any = {
28
+ status_code: status,
29
+ error: true,
30
+ message,
31
+ timestamp: new Date().toISOString(),
32
+ path: request.url,
33
+ method: request.method
34
+ };
35
+
36
+ if (process.env.NODE_ENV !== 'prod') {
37
+ console.error('🚨 Error Details:', exception);
38
+ }
39
+
40
+ return response.status(status).json({
41
+ status_code: status,
42
+ error: true,
43
+ message
44
+ });
45
+ }
46
+ }
@@ -0,0 +1,18 @@
1
+ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
2
+ import { CustomException } from '../exceptions/custom.exception';
3
+ import { ERROR_MSG } from '../utils/constants';
4
+
5
+ @Injectable()
6
+ export class CheckPermissionGuard implements CanActivate {
7
+ constructor(private readonly screen: string) {}
8
+
9
+ canActivate(context: ExecutionContext): boolean {
10
+ const request = context.switchToHttp().getRequest();
11
+ const user = request.user;
12
+
13
+ if (!user.permissions.some((p) => p.screen === this.screen && p.value))
14
+ throw new CustomException(ERROR_MSG.UNAUTHORIZED);
15
+
16
+ return true;
17
+ }
18
+ }
@@ -0,0 +1,20 @@
1
+ import { Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { NextFunction } from 'express';
3
+ import { Unauthorized } from '../exceptions/custom.exception';
4
+ import { AuthService } from 'src/module/auth/auth.service';
5
+
6
+ @Injectable()
7
+ export class AuthMiddleware implements NestMiddleware {
8
+ constructor(private readonly authService: AuthService) {}
9
+ async use(req: Request, res: Response, next: NextFunction) {
10
+ const authHeader = req.headers['authorization'];
11
+ if (!authHeader) throw new Unauthorized();
12
+
13
+ const token = authHeader.split(' ')[1];
14
+ const user = await this.authService.verifyToken(token);
15
+ if (!user) throw new Unauthorized();
16
+
17
+ req['user'] = user;
18
+ next();
19
+ }
20
+ }
@@ -0,0 +1,61 @@
1
+ import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
2
+ import { validate, ValidationError } from 'class-validator';
3
+ import { plainToInstance } from 'class-transformer';
4
+ import { CustomException } from '../exceptions/custom.exception';
5
+
6
+ @Injectable()
7
+ export class ValidationPipe implements PipeTransform<any> {
8
+ async transform(value: any, { metatype }: ArgumentMetadata) {
9
+ const trimmedValue = this.trimStrings(value);
10
+ if (!metatype || !this.toValidate(metatype)) return trimmedValue;
11
+
12
+ const object = plainToInstance(metatype, trimmedValue);
13
+ const errors = await validate(object, {
14
+ whitelist: true,
15
+ forbidNonWhitelisted: true
16
+ });
17
+
18
+ if (errors.length > 0) {
19
+ const errorMessage = this.formatErrors(errors);
20
+ throw new CustomException(errorMessage);
21
+ }
22
+
23
+ return object;
24
+ }
25
+
26
+ private trimStrings(value: any): any {
27
+ if (typeof value === 'string') {
28
+ return value.replace(/\s+/g, ' ').trim();
29
+ } else if (Array.isArray(value)) {
30
+ return value.map((item) => this.trimStrings(item));
31
+ } else if (value && typeof value === 'object' && value !== null) {
32
+ const trimmedObject: Record<string, any> = {};
33
+ for (const key in value) {
34
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
35
+ if (key === 'email')
36
+ trimmedObject[key] = this.trimStrings(value[key]).toLowerCase();
37
+ else trimmedObject[key] = this.trimStrings(value[key]);
38
+ }
39
+ }
40
+ return trimmedObject;
41
+ }
42
+ return value;
43
+ }
44
+
45
+ private toValidate(metatype: Function): boolean {
46
+ const types: Function[] = [String, Boolean, Number, Array, Object];
47
+ return !types.includes(metatype);
48
+ }
49
+
50
+ private formatErrors(errors: ValidationError[]): string {
51
+ return errors
52
+ .map((error) => {
53
+ if (error.constraints) {
54
+ return Object.values(error.constraints).join(', ');
55
+ } else if (error.children) {
56
+ return this.formatErrors(error.children);
57
+ }
58
+ })
59
+ .join(', ');
60
+ }
61
+ }
@@ -0,0 +1,36 @@
1
+ export const REQ_ENV = ['JWT_SECRET', 'DATABASE_URL'];
2
+
3
+ const MESSAGES = {
4
+ SUCCESS_MSG: {
5
+ REGISTERED: 'Registered successfully',
6
+ LOG_IN: 'Log in successfully',
7
+ LOGGED_OUT: 'Logged out successfully',
8
+
9
+ CREATED: 'Created successfully',
10
+ UPDATED: 'Updated successfully',
11
+ DELETED: 'Deleted successfully',
12
+ FETCHED: 'Fetched successfully'
13
+ },
14
+ ERROR_MSG: {
15
+ JSON_WEB_TOKEN_ERROR: 'JsonWebTokenError',
16
+ TOKEN_EXPIRED_ERROR: 'TokenExpiredError',
17
+ TOKEN_EXPIRED: 'Token has expired!',
18
+
19
+ EMAIL_EXISTS: 'Email already exists with another user',
20
+ INVALID_PASSWORD: 'Invalid password',
21
+ UNAUTHORIZED: 'Unauthorized',
22
+ FORBIDDEN: 'Forbidden',
23
+ INTERNAL_SERVER_ERROR: 'Internal server error',
24
+ INVALID_TOKEN: 'Token Expired'
25
+ }
26
+ } as const;
27
+
28
+ export const { SUCCESS_MSG, ERROR_MSG } = MESSAGES;
29
+
30
+ export const RESOURCE_NAME = {
31
+ USER: 'User',
32
+ };
33
+
34
+ export const DEFAULT_LIMIT = {
35
+
36
+ }
@@ -0,0 +1,43 @@
1
+ import { HttpStatus } from '@nestjs/common';
2
+ import bcrypt from 'bcrypt';
3
+
4
+ export const encryptPassword = async (password: string): Promise<string> =>
5
+ await bcrypt.hash(password, 10);
6
+
7
+ export const comparePassword = async (
8
+ password: string,
9
+ hash: string
10
+ ): Promise<boolean> => await bcrypt.compare(password, hash);
11
+
12
+ export function checkEnvVariables(requiredEnvVars: string[]) {
13
+ requiredEnvVars.forEach((envVar) => {
14
+ if (!process.env[envVar]) {
15
+ console.error(
16
+ `\x1b[31mMissing required environment variable: ${envVar}\x1b[0m`
17
+ );
18
+ process.exit(1);
19
+ }
20
+ });
21
+ }
22
+
23
+ export const successResponse = <T>(message: string) =>
24
+ ({
25
+ status_code: HttpStatus.OK,
26
+ error: false,
27
+ message
28
+ }) as const;
29
+
30
+ export const successResponseWithResult = <T>(message: string, result: T) =>
31
+ ({
32
+ status_code: HttpStatus.OK,
33
+ error: false,
34
+ message,
35
+ result
36
+ }) as const;
37
+
38
+ export function formatResponse<T>(data: T[], count: number) {
39
+ return {
40
+ data,
41
+ count
42
+ } as const;
43
+ }
@@ -0,0 +1,8 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { JwtModule } from './jwt/jwt.module';
3
+ import { PrismaModule } from './prisma/prisma.module';
4
+
5
+ @Module({
6
+ imports: [JwtModule, PrismaModule]
7
+ })
8
+ export class CoreModule {}
@@ -0,0 +1,10 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { JwtService as JWTService } from '@nestjs/jwt';
3
+ import { JwtService } from './jwt.service';
4
+
5
+ @Global()
6
+ @Module({
7
+ providers: [JWTService, JwtService],
8
+ exports: [JwtService]
9
+ })
10
+ export class JwtModule {}
@@ -0,0 +1,45 @@
1
+ import { HttpStatus, Injectable } from '@nestjs/common';
2
+ import { JwtService as JWTService } from '@nestjs/jwt';
3
+ import { CustomException } from 'src/common/exceptions/custom.exception';
4
+ import { ERROR_MSG } from 'src/common/utils/constants';
5
+
6
+ @Injectable()
7
+ export class JwtService {
8
+ private readonly secret: string;
9
+ private readonly expiresIn: string;
10
+
11
+ constructor(private readonly jwtService: JWTService) {
12
+ const { JWT_SECRET, JWT_EXP_IN } = process.env;
13
+ this.secret = JWT_SECRET as string;
14
+ this.expiresIn = JWT_EXP_IN as string;
15
+ }
16
+
17
+ async signAsync(payload: any, expiresIn?: string): Promise<string> {
18
+ return this.jwtService.signAsync(payload, {
19
+ secret: this.secret,
20
+ expiresIn: expiresIn || this.expiresIn
21
+ });
22
+ }
23
+
24
+ async verifyAsync(token: string): Promise<any> {
25
+ try {
26
+ const decoded = await this.jwtService.verifyAsync(token, {
27
+ secret: this.secret
28
+ });
29
+
30
+ return decoded;
31
+ } catch (error) {
32
+ if (error.name === ERROR_MSG.TOKEN_EXPIRED_ERROR) {
33
+ throw new CustomException(
34
+ ERROR_MSG.TOKEN_EXPIRED,
35
+ HttpStatus.UNAUTHORIZED
36
+ );
37
+ } else {
38
+ throw new CustomException(
39
+ ERROR_MSG.UNAUTHORIZED,
40
+ HttpStatus.UNAUTHORIZED
41
+ );
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { PrismaService } from './prisma.service';
3
+
4
+ @Global()
5
+ @Module({
6
+ providers: [PrismaService],
7
+ exports: [PrismaService]
8
+ })
9
+ export class PrismaModule {}
@@ -0,0 +1,17 @@
1
+ import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
2
+ import { PrismaClient } from '@prisma/client';
3
+
4
+ @Injectable()
5
+ export class PrismaService
6
+ extends PrismaClient
7
+ implements OnModuleInit, OnModuleDestroy
8
+ {
9
+ async onModuleInit() {
10
+ await this.$connect();
11
+ console.log('Connected to Prisma Client');
12
+ }
13
+
14
+ async onModuleDestroy() {
15
+ await this.$disconnect();
16
+ }
17
+ }
@@ -0,0 +1,27 @@
1
+ // This is your Prisma schema file,
2
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
3
+
4
+ // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
5
+ // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
6
+
7
+ generator client {
8
+ provider = "prisma-client-js"
9
+ }
10
+
11
+ datasource db {
12
+ provider = "mysql"
13
+ url = env("DATABASE_URL")
14
+ }
15
+
16
+ model User {
17
+ id Int @id @default(autoincrement())
18
+ name String
19
+ email String @unique
20
+ password String
21
+ isDeleted Boolean @default(false)
22
+ createdAt DateTime @default(now())
23
+ updatedAt DateTime @updatedAt
24
+
25
+ @@index([email, createdAt])
26
+ @@map("users")
27
+ }
@@ -0,0 +1,26 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { AppModule } from './app.module';
3
+ import { ValidationPipe } from './common/pipes/validation.pipe';
4
+ import { HttpExceptionFilter } from './common/filters/http-exception.filter';
5
+ import { checkEnvVariables } from './common/utils/helper';
6
+ import { REQ_ENV } from './common/utils/constants';
7
+
8
+ async function bootstrap() {
9
+ checkEnvVariables(REQ_ENV);
10
+ const app = await NestFactory.create(AppModule);
11
+ app.enableCors({
12
+ origin: '*',
13
+ methods: 'GET,PUT,POST,PATCH,DELETE',
14
+ credentials: true
15
+ });
16
+
17
+ app.useGlobalPipes(new ValidationPipe());
18
+
19
+ app.useGlobalFilters(new HttpExceptionFilter());
20
+
21
+ const port = process.env.PORT ?? 5000;
22
+ await app.listen(port, () => {
23
+ console.log(`🚀 Server is listening on port ${port}`);
24
+ });
25
+ }
26
+ bootstrap();
@@ -0,0 +1,30 @@
1
+ import { Body, Controller, Get, Post, Put } from '@nestjs/common';
2
+ import { AuthService } from './auth.service';
3
+ import { LoginDto, RegisterDto, UpdateUserDto } from './dto';
4
+ import { CurrentUser } from 'src/common/decorators/user.decorator';
5
+ import { User } from '@prisma/client';
6
+
7
+ @Controller('auth')
8
+ export class AuthController {
9
+ constructor(private readonly authService: AuthService) {}
10
+
11
+ @Post('register')
12
+ register(@Body() body: RegisterDto) {
13
+ return this.authService.register(body);
14
+ }
15
+
16
+ @Post('login')
17
+ login(@Body() body: LoginDto) {
18
+ return this.authService.login(body);
19
+ }
20
+
21
+ @Get('profile')
22
+ profile(@CurrentUser() user: Partial<User>) {
23
+ return this.authService.profile(user);
24
+ }
25
+
26
+ @Put('update')
27
+ update(@Body() body: UpdateUserDto, @CurrentUser() user: Partial<User>) {
28
+ return this.authService.update(body, user.id as number);
29
+ }
30
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { AuthController } from './auth.controller';
3
+ import { AuthService } from './auth.service';
4
+
5
+ @Module({
6
+ controllers: [AuthController],
7
+ providers: [AuthService],
8
+ exports: [AuthService]
9
+ })
10
+ export class AuthModule {}
@@ -0,0 +1,83 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { PrismaService } from 'src/core/prisma/prisma.service';
3
+ import { LoginDto, LoginResDto, RegisterDto, UpdateUserDto } from './dto';
4
+ import {
5
+ comparePassword,
6
+ encryptPassword,
7
+ successResponse,
8
+ successResponseWithResult
9
+ } from 'src/common/utils/helper';
10
+ import {
11
+ ERROR_MSG,
12
+ RESOURCE_NAME,
13
+ SUCCESS_MSG
14
+ } from 'src/common/utils/constants';
15
+ import {
16
+ CustomException,
17
+ ResourceNotFound
18
+ } from 'src/common/exceptions/custom.exception';
19
+ import { ResponseDto } from 'src/common/dtos/common.dto';
20
+ import { JwtService } from 'src/core/jwt/jwt.service';
21
+ import { User } from '@prisma/client';
22
+
23
+ @Injectable()
24
+ export class AuthService {
25
+ constructor(
26
+ private readonly prisma: PrismaService,
27
+ private readonly jwtService: JwtService
28
+ ) {}
29
+
30
+ async register(body: RegisterDto): Promise<ResponseDto> {
31
+ const existingUser = await this.findByEmail(body.email);
32
+ if (existingUser) throw new CustomException(ERROR_MSG.EMAIL_EXISTS);
33
+
34
+ body.password = await encryptPassword(body.password);
35
+ await this.prisma.user.create({ data: body });
36
+
37
+ return successResponse(SUCCESS_MSG.REGISTERED);
38
+ }
39
+
40
+ async login(body: LoginDto): Promise<LoginResDto> {
41
+ const user = await this.findByEmail(body.email);
42
+ if (!user) throw new ResourceNotFound(RESOURCE_NAME.USER);
43
+
44
+ if (!(await comparePassword(body.password, user.password)))
45
+ throw new CustomException(ERROR_MSG.INVALID_PASSWORD);
46
+
47
+ const token = await this.jwtService.signAsync({ id: user.id });
48
+ return successResponseWithResult(SUCCESS_MSG.LOG_IN, { token });
49
+ }
50
+
51
+ profile(user: Partial<User>) {
52
+ return successResponseWithResult(SUCCESS_MSG.FETCHED, user);
53
+ }
54
+
55
+ async verifyToken(token: string): Promise<Partial<User>> {
56
+ const { id } = await this.jwtService.verifyAsync(token);
57
+ return this.findById(id);
58
+ }
59
+
60
+ async update(data: UpdateUserDto, id: number): Promise<ResponseDto> {
61
+ await this.findById(id);
62
+
63
+ const existingUser = await this.findByEmail(data.email);
64
+ if (existingUser && existingUser.id !== id)
65
+ throw new CustomException(ERROR_MSG.EMAIL_EXISTS);
66
+
67
+ await this.prisma.user.update({ where: { id }, data });
68
+ return successResponse(SUCCESS_MSG.UPDATED);
69
+ }
70
+
71
+ private async findById(id: number): Promise<Partial<User>> {
72
+ const user = await this.prisma.user.findUnique({
73
+ where: { id, isDeleted: false },
74
+ select: { id: true, name: true, email: true }
75
+ });
76
+ if (!user) throw new ResourceNotFound(RESOURCE_NAME.USER);
77
+ return user;
78
+ }
79
+
80
+ private async findByEmail(email: string): Promise<User | null> {
81
+ return this.prisma.user.findUnique({ where: { email } });
82
+ }
83
+ }