auth fixes
This commit is contained in:
parent
5c24732ae4
commit
159f8a0988
@ -93,7 +93,6 @@ CREATE TABLE "rules_details_ref" (
|
||||
"deletedAt" DATE,
|
||||
"version" NUMERIC
|
||||
);
|
||||
|
||||
CREATE TABLE "users" (
|
||||
"id" BIGSERIAL PRIMARY KEY NOT NULL,
|
||||
"email" TEXT UNIQUE NOT NULL,
|
||||
@ -102,6 +101,8 @@ CREATE TABLE "users" (
|
||||
"name" TEXT,
|
||||
"phoneNumber" TEXT,
|
||||
"primaryRole" TEXT,
|
||||
"resetCode" TEXT,
|
||||
"resetCodeExpires" TIMESTAMP,
|
||||
"status" TEXT,
|
||||
"validFrom" DATE,
|
||||
"validTill" DATE,
|
||||
@ -476,3 +477,20 @@ CREATE TABLE "event_analytics" (
|
||||
"version" NUMERIC
|
||||
);
|
||||
|
||||
CREATE TABLE "locations" (
|
||||
"id" BIGSERIAL PRIMARY KEY NOT NULL,
|
||||
"name" TEXT,
|
||||
"code" TEXT,
|
||||
"images" TEXT[],
|
||||
"status" TEXT,
|
||||
"validFrom" DATE,
|
||||
"validTill" DATE,
|
||||
"createdAt" DATE,
|
||||
"updatedAt" DATE,
|
||||
"createdBy" TEXT,
|
||||
"modifiedBy" TEXT,
|
||||
"deletedAt" DATE,
|
||||
"version" NUMERIC
|
||||
);
|
||||
|
||||
|
||||
|
||||
39
package-lock.json
generated
39
package-lock.json
generated
@ -22,6 +22,7 @@
|
||||
"@nestjs/websockets": "^10.4.15",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^16.3.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"ioredis": "^5.6.0",
|
||||
@ -4172,6 +4173,28 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser/node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@ -13800,6 +13823,22 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
|
||||
"requires": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"@nestjs/websockets": "^10.4.15",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"dotenv": "^16.3.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"ioredis": "^5.6.0",
|
||||
|
||||
@ -23,6 +23,7 @@ import { TimeSlotModule } from './timeSlot/timeSlot.module';
|
||||
import { PushSubscriptionModule } from './push-subscription/push-subscription.module';
|
||||
import { RedisModule } from './redis/redis.module';
|
||||
import { BookingGatewayModule } from './booking-gateway/booking-gateway.module';
|
||||
import { LocationsModule } from './locations/locations.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -44,7 +45,8 @@ import { BookingGatewayModule } from './booking-gateway/booking-gateway.module';
|
||||
UserModule,
|
||||
PushSubscriptionModule,
|
||||
RedisModule,
|
||||
BookingGatewayModule
|
||||
BookingGatewayModule,
|
||||
LocationsModule
|
||||
|
||||
],
|
||||
controllers: [AppController, AppConfigController],
|
||||
|
||||
@ -6,10 +6,13 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { GoogleOauthGuard } from 'src/google-oauth/google-oauth.guard';
|
||||
import { User } from 'src/user/user.entity';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
import { ForgotPasswordDto, LoginDto, ResetPasswordDto, SignupDto, VerifyCodeDto } from './auth.dto';
|
||||
import { MailService } from 'src/mail/mail.service';
|
||||
|
||||
@ApiTags('Auth')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService, private userService: UserService) { }
|
||||
constructor(private authService: AuthService, private userService: UserService, private mailService: MailService) { }
|
||||
|
||||
@Post('login')
|
||||
@ApiOperation({ summary: 'Login' })
|
||||
@ -30,7 +33,7 @@ export class AuthController {
|
||||
"data": null
|
||||
}
|
||||
})
|
||||
async login(@Body() userDto: { email: string; password: string }, @Res() res: Response) {
|
||||
async login(@Body() userDto: LoginDto, @Res() res: Response) {
|
||||
const user = await this.authService.validateUser(userDto);
|
||||
if (!user) {
|
||||
const errorResponse = new GenericResponse({
|
||||
@ -42,12 +45,26 @@ export class AuthController {
|
||||
res.status(404).send(errorResponse);
|
||||
}
|
||||
const tokens = await this.authService.login(user);
|
||||
const httpResponse = new GenericResponse(null, tokens);
|
||||
|
||||
res.header('Authorization', 'Bearer ' + tokens.access_token);
|
||||
|
||||
res.cookie('refresh_token', tokens.refresh_token, {
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
path: '/'
|
||||
});
|
||||
const httpResponse = new GenericResponse(null, {
|
||||
// id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
userTypeCode: user.userTypeCode,
|
||||
});
|
||||
res.status(200).send(httpResponse);
|
||||
}
|
||||
|
||||
@Post('signup')
|
||||
async signup(@Body() userDto: User, @Res() res: Response) {
|
||||
async signup(@Body() userDto: SignupDto, @Res() res: Response) {
|
||||
const user = await this.userService.findByEmail(userDto.email);
|
||||
if (user) {
|
||||
const errorResponse = new GenericResponse({
|
||||
@ -56,37 +73,112 @@ export class AuthController {
|
||||
exceptionMessage: 'ERR.NOT_FOUND',
|
||||
stackTrace: 'User already exists'
|
||||
}, null);
|
||||
res.status(404).send(errorResponse);
|
||||
res.status(401).send(errorResponse);
|
||||
}
|
||||
const userCreated = await this.userService.upsert(userDto, true);
|
||||
const tokens = await this.authService.login(userCreated);//MARK: NEED MAILER TO SEND CONFIRMATION EMAIL
|
||||
const httpResponse = new GenericResponse(null, tokens);
|
||||
const tokens = await this.authService.login(userCreated);
|
||||
res.header('Authorization', 'Bearer ' + tokens.access_token);
|
||||
res.cookie('refresh_token', tokens.refresh_token, {
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
path: '/'
|
||||
})
|
||||
const httpResponse = new GenericResponse(null,
|
||||
{
|
||||
email: tokens.email,
|
||||
name: tokens.name,
|
||||
userTypeCode: tokens.userTypeCode,
|
||||
}
|
||||
);
|
||||
res.status(200).send(httpResponse);
|
||||
}
|
||||
|
||||
|
||||
@Post('refresh')
|
||||
async refresh(@Body() body: { refresh_token: string }, @Res() res: Response) {
|
||||
const newToken = await this.authService.refreshAccessToken(body.refresh_token);
|
||||
// console.log("new token is",newToken);
|
||||
if (!newToken) {
|
||||
const errorResponse = new GenericResponse({
|
||||
async refresh(@Req() req: Request, @Res() res: Response) {
|
||||
console.log("req.cookies");
|
||||
console.log(req.cookies);
|
||||
console.log('headers:', req.headers.cookie);
|
||||
|
||||
const refreshToken = req.cookies['refresh_token'];
|
||||
if (!refreshToken) {
|
||||
return res.status(401).json({ message: 'Refresh token missing' });
|
||||
}
|
||||
try {
|
||||
const newToken = await this.authService.refreshAccessToken(refreshToken);
|
||||
|
||||
res.header('Authorization', 'Bearer ' + newToken.access_token);
|
||||
return res.status(200).json(new GenericResponse(null, {
|
||||
email: newToken.email,
|
||||
name: newToken.name,
|
||||
userTypeCode: newToken.userTypeCode
|
||||
}));
|
||||
} catch (err) {
|
||||
return res.status(403).json({
|
||||
message: 'Invalid or expired refresh token',
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Post('forgot-password')
|
||||
@ApiOperation({ summary: 'Send reset code to email' })
|
||||
async forgotPassword(@Body() dto: ForgotPasswordDto, @Res() res: Response) {
|
||||
await this.authService.sendResetCode(dto.email);
|
||||
return res.status(200).send(new GenericResponse(null, {
|
||||
message: 'If your email is registered, a code has been sent.',
|
||||
}));
|
||||
}
|
||||
|
||||
@Post('verify-reset-code')
|
||||
@ApiOperation({ summary: 'Verify 4-digit reset code' })
|
||||
async verifyCode(@Body() dto: VerifyCodeDto, @Res() res: Response) {
|
||||
const isValid = await this.authService.verifyResetCode(dto.email, dto.code);
|
||||
if (!isValid) {
|
||||
return res.status(400).send(new GenericResponse({
|
||||
exception: true,
|
||||
exceptionSeverity: 'HIGH',
|
||||
exceptionMessage: 'ERR.NOT_FOUND',
|
||||
stackTrace: 'Token not found'
|
||||
}, null)
|
||||
res.status(404).send(errorResponse);
|
||||
exceptionMessage: 'ERR.INVALID_OR_EXPIRED_CODE',
|
||||
stackTrace: 'Code is invalid or expired',
|
||||
}, null));
|
||||
}
|
||||
const httpResponse = new GenericResponse(null, newToken);
|
||||
res.status(201).send(httpResponse);
|
||||
|
||||
return res.status(200).send(new GenericResponse(null, {
|
||||
message: 'Code verified successfully.',
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@Post('reset-password')
|
||||
@ApiOperation({ summary: 'Reset password using code' })
|
||||
async resetPassword(@Body() dto: ResetPasswordDto, @Res() res: Response) {
|
||||
const success = await this.authService.resetPasswordWithCode(dto.email, dto.code, dto.password);
|
||||
|
||||
if (!success) {
|
||||
return res.status(400).send(new GenericResponse({
|
||||
exception: true,
|
||||
exceptionSeverity: 'HIGH',
|
||||
exceptionMessage: 'ERR.INVALID_OR_EXPIRED_CODE',
|
||||
stackTrace: 'Code is invalid or expired',
|
||||
}, null));
|
||||
}
|
||||
|
||||
return res.status(200).send(new GenericResponse(null, {
|
||||
message: 'Password has been reset successfully.',
|
||||
}));
|
||||
}
|
||||
|
||||
@Delete('logout')
|
||||
delete(@Body() body: { refresh_token: string }, @Res() res: Response) {
|
||||
const deleteToken = this.authService.logout(body.refresh_token);
|
||||
delete(@Res() res: Response, @Req() req: Request) {
|
||||
const refreshToken = req.cookies['refresh_token'];
|
||||
const deleteToken = this.authService.logout(refreshToken);
|
||||
res.status(200).send(deleteToken);
|
||||
}
|
||||
|
||||
@Post('verifyAccess')
|
||||
// @Post('verifyAccess')
|
||||
async verifyAccessToken(@Body() body: { access_token: string }, @Res() res: Response) {
|
||||
const token = await this.authService.verifyAccessToken(body.access_token);
|
||||
if (token) {
|
||||
@ -103,7 +195,7 @@ export class AuthController {
|
||||
}
|
||||
}
|
||||
|
||||
@Post('verifyRefresh')
|
||||
// @Post('verifyRefresh')
|
||||
async verifyRefreshToken(@Body() body: { refresh_token: string }, @Res() res: Response) {
|
||||
const token = await this.authService.verifyRefreshToken(body.refresh_token);
|
||||
if (token) {
|
||||
@ -133,8 +225,13 @@ export class AuthController {
|
||||
async googleOauthRedirect(@Req() req: Request, @Res() res: Response) {
|
||||
console.log("inside google redirect");
|
||||
const httpResponse = await this.authService.googleOauthRedirect(req.user);
|
||||
return res.status(httpResponse.statusCode).send(httpResponse);
|
||||
res.header('Authorization', 'Bearer ' + httpResponse.access_token);
|
||||
res.cookie('refresh_token', httpResponse.refresh_token, {
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
path: '/'
|
||||
})
|
||||
return res.status(httpResponse.statusCode).send();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
101
src/auth/auth.dto.ts
Normal file
101
src/auth/auth.dto.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, MinLength } from 'class-validator';
|
||||
|
||||
export class SignupDto {
|
||||
@ApiProperty({
|
||||
description: 'User email address',
|
||||
example: 'user@example.com',
|
||||
})
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'User phone number in international format',
|
||||
example: '+1234567890',
|
||||
})
|
||||
@IsPhoneNumber(null)
|
||||
@IsOptional()
|
||||
@IsNotEmpty()
|
||||
phoneNumber?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'User full name (optional)',
|
||||
example: 'John Doe',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@ApiProperty({
|
||||
// description: 'Password with a minimum of 6 characters',
|
||||
example: 'securePassword123',
|
||||
})
|
||||
@IsString()
|
||||
// @MinLength(6)
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'User type code',
|
||||
example: 'ADMIN',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
userTypeCode: string
|
||||
}
|
||||
|
||||
|
||||
export class LoginDto {
|
||||
@ApiProperty({
|
||||
description: 'User email address',
|
||||
example: 'user@example.com',
|
||||
})
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
// description: 'Password with a minimum of 6 characters',
|
||||
example: 'securePassword123',
|
||||
})
|
||||
@IsString()
|
||||
// @MinLength(6)
|
||||
@IsNotEmpty()
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class ForgotPasswordDto {
|
||||
@ApiProperty({ example: 'user@example.com' })
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class VerifyCodeDto {
|
||||
@ApiProperty({ example: 'user@example.com' })
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: '1234' })
|
||||
@IsString()
|
||||
code: string;
|
||||
}
|
||||
|
||||
|
||||
export class ResetPasswordDto {
|
||||
@ApiProperty({ example: 'user@example.com' })
|
||||
@IsEmail()
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ example: '1234' })
|
||||
@IsString()
|
||||
code: string;
|
||||
|
||||
@ApiProperty({ example: 'newStrongPassword123' })
|
||||
@IsString()
|
||||
@MinLength(6)
|
||||
password: string;
|
||||
}
|
||||
@ -8,6 +8,7 @@ import { JwtStrategy } from 'src/jwt/jwt.strategy';
|
||||
import { Utility } from 'src/common/Utility';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { GoogleStrategy } from 'src/google-oauth/google.strategy';
|
||||
import { MailModule } from 'src/mail/mail.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -25,6 +26,7 @@ import { GoogleStrategy } from 'src/google-oauth/google.strategy';
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
UserModule,
|
||||
MailModule
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy,GoogleStrategy],
|
||||
|
||||
@ -7,11 +7,16 @@ import RefreshToken from 'src/jwt/refresh-token.entity';
|
||||
import { User } from 'src/user/user.entity';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
import { Request } from 'express';
|
||||
import * as crypto from 'crypto';
|
||||
import { MailService } from 'src/mail/mail.service';
|
||||
import { instanceToPlain } from 'class-transformer';
|
||||
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
||||
constructor(private userService: UserService, private jwtService: JwtService) { }
|
||||
constructor(private userService: UserService, private jwtService: JwtService, private mailService: MailService) { }
|
||||
|
||||
private signToken(payload: any, type: 'accessToken' | 'refreshToken') {
|
||||
const config = Utility.jwtConfig[type];
|
||||
@ -33,16 +38,23 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async validateUser(payload: any) {
|
||||
return this.userService.findByEmail(payload.email);
|
||||
return this.userService.findByEmailWithPassword(payload.email, payload.password);
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
const { password, ...rest } = user;
|
||||
const payload = { rest };
|
||||
// const plainUser = instanceToPlain(user);
|
||||
const payload = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
userTypeCode: user.userTypeCode,
|
||||
};
|
||||
const accessToken = this.signToken(payload, 'accessToken');
|
||||
const refreshToken = this.signToken(payload, 'refreshToken');
|
||||
await RefreshToken.create({ email: user.email, token: refreshToken, type: 'jwt' });
|
||||
return {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
userTypeCode: user.userTypeCode,
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
};
|
||||
@ -69,10 +81,18 @@ export class AuthService {
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
const { password, ...rest } = user;
|
||||
const newPayload = { rest };
|
||||
const accessToken = this.signToken({ newPayload }, 'accessToken');
|
||||
return { access_token: accessToken };
|
||||
const newPayload = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
userTypeCode: user.userTypeCode,
|
||||
};
|
||||
const accessToken = this.signToken(newPayload, 'accessToken');
|
||||
return {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
userTypeCode: user.userTypeCode,
|
||||
access_token: accessToken
|
||||
};
|
||||
}
|
||||
|
||||
async verifyRefreshToken(refreshToken: string) {
|
||||
@ -90,6 +110,44 @@ export class AuthService {
|
||||
}
|
||||
|
||||
|
||||
async sendResetCode(email: string): Promise<void> {
|
||||
const user = await this.userService.findByEmail(email);
|
||||
if (!user) return;
|
||||
const plainUser = user.get({ plain: true });
|
||||
console.log("user", plainUser);
|
||||
const code = Math.floor(1000 + Math.random() * 9000).toString();
|
||||
const expires = new Date(Date.now() + 10 * 60 * 1000);
|
||||
// console.log("code", code);
|
||||
// console.log("expires", expires);
|
||||
user.resetCode = code;
|
||||
user.resetCodeExpires = expires;
|
||||
const updatedUser = await this.userService.update(plainUser);
|
||||
console.log("updatedUser", updatedUser);
|
||||
const subject = 'Your Password Reset Code';
|
||||
const html = `<p>Your reset code is <strong>${code}</strong>. It will expire in 10 minutes.</p>`;
|
||||
await this.mailService.sendMail(email, subject, `Code: ${code}`, html);
|
||||
}
|
||||
|
||||
async resetPasswordWithCode(email: string, code: string, password: string): Promise<boolean> {
|
||||
const userObj = await this.userService.findByEmail(email);
|
||||
const user = userObj.get({ plain: true });
|
||||
if (!user || user.resetCode != code) return false;
|
||||
|
||||
user.password = this.encryptPassword(password);
|
||||
user.resetCode = null;
|
||||
user.resetCodeExpires = null;
|
||||
await this.userService.update(user);
|
||||
return true;
|
||||
}
|
||||
|
||||
async verifyResetCode(email: string, code: string): Promise<boolean> {
|
||||
const user = await this.userService.findByEmail(email);
|
||||
let plainUser = user.get({ plain: true });
|
||||
console.log("plainUser", plainUser);
|
||||
if (!plainUser || !plainUser.resetCode || !plainUser.resetCodeExpires) return false;
|
||||
return plainUser.resetCode == code.toString();
|
||||
}
|
||||
|
||||
//google services
|
||||
|
||||
async googleOauthRedirect(user) {
|
||||
@ -101,25 +159,35 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
console.log("user.email in service is", user.email);
|
||||
let existingUser = await User.findOne({ where: { email: user.email } });
|
||||
// let existingUser = await User.findOne({ where: { email: user.email } });
|
||||
let existingUser = await this.userService.findByEmail(user.email);
|
||||
if (!existingUser) {
|
||||
existingUser = await User.create({
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
userTypeCode: 'user'
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const payload = existingUser.get();
|
||||
const { password, ...rest } = payload
|
||||
const newuser = await this.userService.findByEmail(user.email);
|
||||
const rest = {
|
||||
email: newuser.email,
|
||||
name: newuser.name,
|
||||
userTypeCode: newuser.userTypeCode,
|
||||
};
|
||||
|
||||
const payload = existingUser.get({ plain: true });
|
||||
// const { } = payload
|
||||
const accessToken = this.signToken(rest, 'accessToken');
|
||||
const refreshToken = this.signToken(rest, 'refreshToken');
|
||||
await RefreshToken.create({ email: payload.email, token: refreshToken, type: 'jwt' });
|
||||
return {
|
||||
statusCode: 200,
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken
|
||||
}
|
||||
}
|
||||
|
||||
encryptPassword(password: string) {
|
||||
return Buffer.from(password).toString('base64');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Table, Column, Model, DataType } from 'sequelize-typescript';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
@Table({ tableName: 'cast', paranoid: true })
|
||||
@Table({ tableName: 'cast_members', paranoid: true })
|
||||
export default class Cast extends Model {
|
||||
|
||||
@ApiProperty({ type: Number })
|
||||
|
||||
30
src/locations/dto/create-location.dto.ts
Normal file
30
src/locations/dto/create-location.dto.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class CreateLocationDto {
|
||||
@ApiProperty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty()
|
||||
code: string;
|
||||
|
||||
@ApiProperty({ type: [String], required: false })
|
||||
images?: string[];
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
status?: string;
|
||||
|
||||
@ApiProperty({ required: false, type: String, format: 'date' })
|
||||
validFrom?: Date;
|
||||
|
||||
@ApiProperty({ required: false, type: String, format: 'date' })
|
||||
validTill?: Date;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
createdBy?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
modifiedBy?: string;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
version?: number;
|
||||
}
|
||||
7
src/locations/dto/update-location.dto.ts
Normal file
7
src/locations/dto/update-location.dto.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { PartialType, ApiProperty } from '@nestjs/swagger';
|
||||
import { CreateLocationDto } from './create-location.dto';
|
||||
|
||||
export class UpdateLocationDto extends PartialType(CreateLocationDto) {
|
||||
@ApiProperty()
|
||||
id: number;
|
||||
}
|
||||
67
src/locations/entities/location.entity.ts
Normal file
67
src/locations/entities/location.entity.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
Table,
|
||||
Column,
|
||||
Model,
|
||||
DataType,
|
||||
PrimaryKey,
|
||||
AutoIncrement,
|
||||
Default,
|
||||
} from 'sequelize-typescript';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
@Table({ tableName: 'locations' })
|
||||
export class Location extends Model {
|
||||
@ApiProperty({ type: Number })
|
||||
@PrimaryKey
|
||||
@AutoIncrement
|
||||
@Column({ type: DataType.BIGINT })
|
||||
id: number;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
code: string;
|
||||
|
||||
@ApiProperty({ type: [String] })
|
||||
@Column({ type: DataType.ARRAY(DataType.TEXT) })
|
||||
images: string[];
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
status: string;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
@Column({ type: DataType.DATEONLY })
|
||||
validFrom: Date;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
@Column({ type: DataType.DATEONLY })
|
||||
validTill: Date;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
@Column({ type: DataType.DATE })
|
||||
createdAt: Date;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
@Column({ type: DataType.DATE })
|
||||
updatedAt: Date;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
createdBy: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
modifiedBy: string;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
@Column({ type: DataType.DATE })
|
||||
deletedAt: Date;
|
||||
|
||||
@ApiProperty({ type: Number })
|
||||
@Column({ type: DataType.NUMBER })
|
||||
version: number;
|
||||
}
|
||||
20
src/locations/locations.controller.spec.ts
Normal file
20
src/locations/locations.controller.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LocationsController } from './locations.controller';
|
||||
import { LocationsService } from './locations.service';
|
||||
|
||||
describe('LocationsController', () => {
|
||||
let controller: LocationsController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [LocationsController],
|
||||
providers: [LocationsService],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<LocationsController>(LocationsController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
||||
41
src/locations/locations.controller.ts
Normal file
41
src/locations/locations.controller.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
|
||||
import { LocationsService } from './locations.service';
|
||||
import { CreateLocationDto } from './dto/create-location.dto';
|
||||
import { UpdateLocationDto } from './dto/update-location.dto';
|
||||
|
||||
@Controller('locations')
|
||||
export class LocationsController {
|
||||
constructor(private readonly locationsService: LocationsService) { }
|
||||
|
||||
@Post()
|
||||
create(@Body() createLocationDto: CreateLocationDto) {
|
||||
return this.locationsService.create(createLocationDto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.locationsService.findAll();
|
||||
}
|
||||
|
||||
@Get('search')
|
||||
async search(@Query('q') query: string) {
|
||||
return this.locationsService.search(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.locationsService.findOne(+id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateLocationDto: UpdateLocationDto) {
|
||||
return this.locationsService.update(+id, updateLocationDto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.locationsService.remove(+id);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
9
src/locations/locations.module.ts
Normal file
9
src/locations/locations.module.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LocationsService } from './locations.service';
|
||||
import { LocationsController } from './locations.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [LocationsController],
|
||||
providers: [LocationsService],
|
||||
})
|
||||
export class LocationsModule {}
|
||||
18
src/locations/locations.service.spec.ts
Normal file
18
src/locations/locations.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LocationsService } from './locations.service';
|
||||
|
||||
describe('LocationsService', () => {
|
||||
let service: LocationsService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [LocationsService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<LocationsService>(LocationsService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
56
src/locations/locations.service.ts
Normal file
56
src/locations/locations.service.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { CreateLocationDto } from './dto/create-location.dto';
|
||||
import { UpdateLocationDto } from './dto/update-location.dto';
|
||||
import { Location } from './entities/location.entity';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
@Injectable()
|
||||
export class LocationsService {
|
||||
async create(createLocationDto: CreateLocationDto) {
|
||||
return await Location.create(createLocationDto as any);
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
return await Location.findAll();
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
const location = await Location.findByPk(id);
|
||||
if (!location) {
|
||||
throw new NotFoundException(`Location with ID ${id} not found`);
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
async update(id: number, updateLocationDto: UpdateLocationDto) {
|
||||
const location = await Location.findByPk(id);
|
||||
if (!location) {
|
||||
throw new NotFoundException(`Location with ID ${id} not found`);
|
||||
}
|
||||
await location.update(updateLocationDto);
|
||||
return location;
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const location = await Location.findByPk(id);
|
||||
if (!location) {
|
||||
throw new NotFoundException(`Location with ID ${id} not found`);
|
||||
}
|
||||
await location.destroy();
|
||||
return { message: `Location with ID ${id} has been deleted.` };
|
||||
}
|
||||
|
||||
async search(q: string) {
|
||||
if (!q) return []; // no query, return empty list or all
|
||||
|
||||
const locations = await Location.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ name: { [Op.iLike]: `%${q}%` } },
|
||||
{ code: { [Op.iLike]: `%${q}%` } }
|
||||
],
|
||||
},
|
||||
});
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
17
src/mail/mail.controller.ts
Normal file
17
src/mail/mail.controller.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { MailService } from './mail.service';
|
||||
|
||||
@Controller('mail')
|
||||
export class MailController {
|
||||
constructor(private readonly mailService: MailService) {}
|
||||
|
||||
@Post()
|
||||
async sendTestEmail(@Body() body: { email: string }) {
|
||||
const { email } = body;
|
||||
return this.mailService.sendMail(
|
||||
email,
|
||||
'Test Email',
|
||||
'This is a test email.',
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { Module } from '@nestjs/common';
|
||||
import { MailService } from './mail.service';
|
||||
import { join } from 'path';
|
||||
import { Utility } from 'src/common/Utility';
|
||||
import { MailController } from './mail.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -23,5 +24,6 @@ import { Utility } from 'src/common/Utility';
|
||||
],
|
||||
providers: [MailService],
|
||||
exports: [MailService], // 👈 export for DI
|
||||
controllers: [MailController],
|
||||
})
|
||||
export class MailModule { }
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
// import { MailerService } from '@nestjs-modules/mailer'/;
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import { Utility } from 'src/common/Utility';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
// constructor(private mailerService: MailerService) { }
|
||||
private transporter: nodemailer.Transporter;
|
||||
|
||||
|
||||
// async sendEmail(templateName: string, subject: string, context: any, toEmail: string, ccEmails?: string[], bccEmails?: string[]) {
|
||||
// await this.mailerService.sendMail({
|
||||
// to: toEmail,
|
||||
// cc: ccEmails,
|
||||
// bcc: bccEmails,
|
||||
// subject: subject,
|
||||
// template: templateName, // `.hbs` extension is appended automatically
|
||||
// context,
|
||||
// })
|
||||
// }
|
||||
constructor() {
|
||||
this.transporter = nodemailer.createTransport(Utility.mailConfig.transport);
|
||||
}
|
||||
|
||||
async sendMail(to: string, subject: string, text: string, html?: string) {
|
||||
const info = await this.transporter.sendMail({
|
||||
from: Utility.mailConfig.defaults.from,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
});
|
||||
|
||||
console.log('Email sent:', info.messageId);
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
10
src/main.ts
10
src/main.ts
@ -4,6 +4,8 @@ import * as bodyParser from 'body-parser';
|
||||
import * as configMaster from './app-config/config.json';
|
||||
import { Utility } from './common/Utility';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
|
||||
|
||||
async function bootstrap() {
|
||||
Utility.appPort = configMaster.local.appConfig.port;
|
||||
@ -25,6 +27,14 @@ async function bootstrap() {
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
app.enableCors(
|
||||
{
|
||||
origin: true,
|
||||
credentials: true,
|
||||
}
|
||||
);
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
|
||||
await app.listen(Utility.appPort);
|
||||
|
||||
@ -33,6 +33,14 @@ export class User extends Model {
|
||||
@Column({ type: DataType.TEXT })
|
||||
primaryRole: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
resetCode: string;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
@Column({ type: DataType.DATE })
|
||||
resetCodeExpires: Date;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column({ type: DataType.TEXT })
|
||||
status: string;
|
||||
@ -73,3 +81,8 @@ export class User extends Model {
|
||||
@HasMany(() => UserAdditionalDetail)
|
||||
additionalDetails: UserAdditionalDetail[];
|
||||
}
|
||||
// @Column({ nullable: true })
|
||||
// resetCode: string;
|
||||
|
||||
// @Column({ type: 'timestamp', nullable: true })
|
||||
// resetCodeExpires: Date;
|
||||
@ -7,30 +7,43 @@ export class UserService {
|
||||
constructor() { }
|
||||
|
||||
async findAll(): Promise<{ rows: User[], count: number }> {
|
||||
// return User.findAndCountAll({ attributes: { exclude: ['password'] } });
|
||||
return User.findAndCountAll();
|
||||
}
|
||||
|
||||
findByPk(id: number): Promise<User> {
|
||||
return User.findByPk(id, {include: UserAdditionalDetail})
|
||||
return User.findByPk(id, { attributes: { exclude: ['password'] } })
|
||||
}
|
||||
|
||||
findOne(user: any): Promise<User> {
|
||||
return User.findOne({where: user as any, include: UserAdditionalDetail})
|
||||
return User.findOne({ where: user as any, attributes: { exclude: ['password'] } })
|
||||
}
|
||||
|
||||
findByEmail(email: string): Promise<User> {
|
||||
return User.findOne({where: {email: email}})
|
||||
async findByEmail(email: string): Promise<User> {
|
||||
return User.findOne({ where: { email: email }, attributes: { exclude: ['password'] } });
|
||||
}
|
||||
|
||||
filter(user: User) : Promise<User[]> {
|
||||
return User.findAll({where: user as any})
|
||||
async findByResetToken(token: string): Promise<User | undefined> {
|
||||
return User.findOne({ where: { resetToken: token, attributes: { exclude: ['password'] } } });
|
||||
}
|
||||
|
||||
|
||||
findByEmailWithPassword(email: string, password: string): Promise<User> {
|
||||
return User.findOne({ where: { email: email, password: this.encryptPassword(password) }, attributes: ['email', 'name', 'id', 'userTypeCode'] })
|
||||
}
|
||||
|
||||
filter(user: any): Promise<User[]> {
|
||||
return User.findAll({ where: user as any, attributes: { exclude: ['password'] } })
|
||||
}
|
||||
|
||||
async remove(id: number): Promise<number> {
|
||||
return User.destroy({where: {id: id}});
|
||||
return User.destroy({ where: { id: id, attributes: { exclude: ['password'] } } });
|
||||
}
|
||||
|
||||
async upsert(userDetail: any, insertIfNotFound: boolean): Promise<User | [affectedCount: number]> {
|
||||
if (userDetail.password) {
|
||||
userDetail.password = this.encryptPassword(userDetail.password);
|
||||
}
|
||||
if (userDetail.id) {
|
||||
const existingUser = await this.findByPk(userDetail.id);
|
||||
if (existingUser) {
|
||||
@ -41,4 +54,15 @@ export class UserService {
|
||||
return User.create(userDetail as any)
|
||||
}
|
||||
}
|
||||
|
||||
async update(userDetail: any): Promise<User | [affectedCount: number]> {
|
||||
if (userDetail.password) {
|
||||
userDetail.password = this.encryptPassword(userDetail.password);
|
||||
}
|
||||
return User.update(userDetail, { where: { id: userDetail.id } });
|
||||
}
|
||||
|
||||
encryptPassword(password: string) {
|
||||
return Buffer.from(password).toString('base64');
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user