auth fixes

This commit is contained in:
harshithnrao 2025-07-25 20:17:33 +05:30
parent 5c24732ae4
commit 159f8a0988
23 changed files with 718 additions and 70 deletions

View File

@ -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
View File

@ -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",

View File

@ -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",

View File

@ -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],

View File

@ -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
View 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;
}

View File

@ -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],

View File

@ -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');
}
}

View File

@ -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 })

View 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;
}

View 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;
}

View 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;
}

View 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();
});
});

View 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);
}
}

View 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 {}

View 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();
});
});

View 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;
}
}

View 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.',
);
}
}

View File

@ -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 { }

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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');
}
}