first commit

This commit is contained in:
2025-11-10 15:05:01 +05:30
commit 0230105958
35 changed files with 13428 additions and 0 deletions

45
src/app/app.module.ts Normal file
View File

@@ -0,0 +1,45 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { JwtModule } from '@nestjs/jwt';
// Common imports
import { PrismaModule } from '../common/database/prisma.module';
import { RolesGuard } from '../common/guards/roles.guard';
// Feature modules
// import { AuthModule } from '../modules/auth/auth.module';
// import { HostModule } from '../modules/host/host.module'; // Add more modules as you create them
@Module({
imports: [
// Global configuration (env variables)
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
// JWT for authentication
JwtModule.register({
global: true,
secret: process.env.JWT_SECRET || 'default_secret',
signOptions: { expiresIn: '1d' },
}),
// Database
PrismaModule,
// App modules
// AuthModule,
// HostModule,
],
providers: [
// Global guards
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}

View File

@@ -0,0 +1,10 @@
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

View File

@@ -0,0 +1,31 @@
import { Injectable, OnModuleInit, OnModuleDestroy, INestApplication } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
super({
datasources: {
db: {
url: process.env.DATABASE_URL,
},
},
log: process.env.NODE_ENV === 'dev' ? ['query', 'info', 'warn', 'error'] : ['error'],
});
}
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
async enableShutdownHooks(app: INestApplication) {
process.on('beforeExit', async () => {
await app.close();
});
}
}

View File

@@ -0,0 +1,12 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { User } from '@prisma/client';
export const CurrentUser = createParamDecorator(
(data: keyof User | undefined, ctx: ExecutionContext): User | any => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);

View File

@@ -0,0 +1,5 @@
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

View File

@@ -0,0 +1,55 @@
import { IsOptional, IsPositive, Min, Max } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';
export class PaginationDto {
@ApiPropertyOptional({
description: 'Page number',
minimum: 1,
default: 1,
})
@IsOptional()
@Type(() => Number)
@IsPositive()
@Min(1)
page?: number = 1;
@ApiPropertyOptional({
description: 'Number of items per page',
minimum: 1,
maximum: 100,
default: 10,
})
@IsOptional()
@Type(() => Number)
@IsPositive()
@Min(1)
@Max(100)
limit?: number = 10;
}
export class PaginationMetaDto {
@ApiPropertyOptional()
page: number;
@ApiPropertyOptional()
limit: number;
@ApiPropertyOptional()
total: number;
@ApiPropertyOptional()
totalPages: number;
@ApiPropertyOptional()
hasNext: boolean;
@ApiPropertyOptional()
hasPrev: boolean;
}
export class PaginatedResponseDto<T> {
data: T[];
meta: PaginationMetaDto;
}

View File

@@ -0,0 +1,25 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
// For now, allow all requests since we don't have role-based auth implemented
// This should be updated when you implement proper role checking
return true;
}
}

View File

@@ -0,0 +1,19 @@
import { User } from '@prisma/client';
export interface JwtPayload {
sub: string;
email: string;
username: string;
role: string;
}
export interface AuthResponse {
user: Omit<User, 'password'>;
accessToken: string;
}
export interface LoginRequest {
email: string;
password: string;
}

View File

@@ -0,0 +1,103 @@
import jwt from 'jsonwebtoken';
import httpStatus from 'http-status';
import { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import ApiError from '../../utils/helper/ApiError';
import config from '../../../config/config';
const prisma = new PrismaClient();
interface DecodedToken {
id: number;
role?: string;
iat: number;
exp: number;
}
interface UserPayload {
id: string;
role?: string;
}
declare module 'express-serve-static-core' {
interface Request {
user?: UserPayload;
}
}
/**
* Verifies JWT and validates Host user (role_xid = 3)
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
if (!decoded?.id) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
}
// ✅ Fetch user from Prisma (Host user only)
const user = await prisma.user.findUnique({
where: { id: decoded.id },
include: { role: true },
});
if (!user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
}
// ✅ Check if user is active
if (!user.isActive) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
);
}
// ✅ Check Host role (role_xid = 3)
if (user.roleXid !== 3) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
);
}
// Attach user to request
req.user = { id: user.id.toString(), role: user.role?.roleName };
resolve();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return reject(
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
);
}
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
);
}
};
/**
* Express middleware — use as `auth()` in routes
*/
const auth =
() =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
verifyCallback(req, resolve, reject);
})
.then(() => next())
.catch((err) => next(err));
};
export default auth;

View File

@@ -0,0 +1,103 @@
import jwt from 'jsonwebtoken';
import httpStatus from 'http-status';
import { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import ApiError from '../../utils/helper/ApiError';
import config from '../../../config/config';
const prisma = new PrismaClient();
interface DecodedToken {
id: number;
role?: string;
iat: number;
exp: number;
}
interface UserPayload {
id: string;
role?: string;
}
declare module 'express-serve-static-core' {
interface Request {
user?: UserPayload;
}
}
/**
* Verifies JWT and validates Host user (role_xid = 3)
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
if (!decoded?.id) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
}
// ✅ Fetch user from Prisma (Host user only)
const user = await prisma.user.findUnique({
where: { id: decoded.id },
include: { role: true },
});
if (!user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
}
// ✅ Check if user is active
if (!user.isActive) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
);
}
// ✅ Check Admin role (role_xid = 2)
if (user.roleXid !== 2) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
);
}
// Attach user to request
req.user = { id: user.id.toString(), role: user.role?.roleName };
resolve();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return reject(
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
);
}
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
);
}
};
/**
* Express middleware — use as `auth()` in routes
*/
const auth =
() =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
verifyCallback(req, resolve, reject);
})
.then(() => next())
.catch((err) => next(err));
};
export default auth;

View File

@@ -0,0 +1,103 @@
import jwt from 'jsonwebtoken';
import httpStatus from 'http-status';
import { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import ApiError from '../../utils/helper/ApiError';
import config from '../../../config/config';
const prisma = new PrismaClient();
interface DecodedToken {
id: number;
role?: string;
iat: number;
exp: number;
}
interface UserPayload {
id: string;
role?: string;
}
declare module 'express-serve-static-core' {
interface Request {
user?: UserPayload;
}
}
/**
* Verifies JWT and validates Host user (role_xid = 3)
*/
const verifyCallback = async (
req: Request,
resolve: (value?: unknown) => void,
reject: (reason?: Error) => void
) => {
const token = req.header('x-auth-token') || req.cookies?.accessToken;
if (!token) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
try {
const decoded = jwt.verify(token, config.jwt.secret) as DecodedToken;
if (!decoded?.id) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Invalid token payload'));
}
// ✅ Fetch user from Prisma (Host user only)
const user = await prisma.user.findUnique({
where: { id: decoded.id },
include: { role: true },
});
if (!user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'User not found'));
}
// ✅ Check if user is active
if (!user.isActive) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Your account is deactivated by admin.')
);
}
// ✅ Check User role (role_xid = 1)
if (user.roleXid !== 1) {
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Access restricted to host users only')
);
}
// Attach user to request
req.user = { id: user.id.toString(), role: user.role?.roleName };
resolve();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return reject(
new ApiError(httpStatus.UNAUTHORIZED, 'Your session has expired. Please log in again.')
);
}
return reject(
new ApiError(httpStatus.FORBIDDEN, 'Invalid or expired authentication token.')
);
}
};
/**
* Express middleware — use as `auth()` in routes
*/
const auth =
() =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
verifyCallback(req, resolve, reject);
})
.then(() => next())
.catch((err) => next(err));
};
export default auth;

View File

@@ -0,0 +1,33 @@
class ApiError<T = unknown> extends Error {
statusCode: number;
data: T | null;
message: string;
success: boolean;
errors: Array<Error>;
isOperational: boolean;
stack?: string;
constructor(
statusCode: number,
message: string = 'Something went wrong',
errors: Array<Error> = [],
isOperational: boolean = true,
stack?: string
) {
super(message);
this.statusCode = statusCode;
this.data = null;
this.message = message;
this.success = false;
this.errors = errors;
this.isOperational = isOperational;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}
export default ApiError;

View File

@@ -0,0 +1,32 @@
import { PaginationDto, PaginationMetaDto } from '../dto/pagination.dto';
export function createPaginationMeta(
paginationDto: PaginationDto,
total: number,
): PaginationMetaDto {
const { page = 1, limit = 10 } = paginationDto;
const totalPages = Math.ceil(total / limit);
return {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1,
};
}
export function paginate<T>(
data: T[],
paginationDto: PaginationDto,
total: number,
) {
const meta = createPaginationMeta(paginationDto, total);
return {
data,
meta,
};
}

185
src/config/config.ts Normal file
View File

@@ -0,0 +1,185 @@
import dotenv from 'dotenv';
import path from 'path';
import * as yup from 'yup';
dotenv.config({ path: path.join(__dirname, '../../.env') });
const envVarsSchema = yup
.object()
.shape({
NODE_ENV: yup
.string()
.oneOf(['production', 'development', 'test'])
.required(),
PORT: yup.number().default(3000),
BASEURL: yup.string().required('Base URL is required'),
// FRONTEND_URL: yup.string().required('Frontend URL is required'),
//JWT
JWT_SECRET: yup.string().required('JWT secret key is required'),
JWT_ACCESS_EXPIRATION_MINUTES: yup
.number()
.default(30)
.required('minutes after which access tokens expire'),
JWT_REFRESH_EXPIRATION_DAYS: yup
.number()
.default(30)
.required('days after which refresh tokens expire'),
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: yup
.number()
.default(10)
.required('minutes after which reset password token expires'),
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: yup
.number()
.default(10)
.required('minutes after which verify email token expires'),
//SMTP and BREVO
// BREVO_SMTP_HOST: yup
// .string()
// .nullable()
// .required('server that will send the emails'),
// BREVO_SMTP_PORT: yup
// .number()
// .nullable()
// .required('port to connect to the email server'),
// BREVO_SMTP_USER: yup
// .string()
// .nullable()
// .required('username for email server'),
// BREVO_SMTP_PASS: yup
// .string()
// .nullable()
// .required('password for email server'),
// BREVO_FROM_EMAIL: yup
// .string()
// .nullable()
// .required('the from field in the emails sent by the app'),
// BREVO_EMAIL_API_KEY: yup
// .string()
// .nullable()
// .required('the from field in the emails sent by the app api key'),
// BREVO_API_BASEURL: yup.string().required('Brevo base URL is required'),
// //one signal
// ONESIGNAL_APPID: yup.string().required('One signal app id is required'),
// ONESIGNAL_REST_APIKEY: yup
// .string()
// .required('One signal api key is required'),
//branch IO
// BRANCH_IO_KEY: yup.string().required('Branch IO key is required'),
// DataBase
DB_USERNAME: yup.string().required('DB Username is required'),
DB_PASSWORD: yup.string().required('DB Password is required'),
DB_DATABASE_NAME: yup.string().required('Database name is required'),
DB_HOSTNAME: yup
.string()
.default('127.0.0.1')
.required('DB Hostname is required'),
DB_PORT: yup.number().default(3306).required('DB Port is required'),
//OTP Bypass
BYPASS_OTP: yup.boolean().default(false).required('Bypass OTP is required'),
})
.noUnknown(true);
// Validate and prepare the configuration
function getConfig() {
try {
// Validate the environment variables
const envVars = envVarsSchema.validateSync(process.env, {
abortEarly: false, // Validate all fields before throwing errors
stripUnknown: true, // Remove fields not in the schema
});
// Return the validated configuration
return {
env: envVars.NODE_ENV,
port: envVars.PORT,
jwt: {
secret: envVars.JWT_SECRET,
accessExpirationMinutes: envVars.JWT_ACCESS_EXPIRATION_MINUTES,
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes:
envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes:
envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
},
database: {
development: {
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
logging: false,
},
test: {
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
logging: false,
socketPath: '/var/run/mysqld/mysqld.sock',
},
production: {
host: envVars.DB_HOSTNAME,
port: envVars.DB_PORT,
username: envVars.DB_USERNAME,
password: envVars.DB_PASSWORD,
database: envVars.DB_DATABASE_NAME,
logging: false,
socketPath: '/var/run/mysqld/mysqld.sock',
},
},
byPassOTP: envVars.BYPASS_OTP,
// BaseURL: envVars.BASEURL,
// FRONTEND_URL: envVars.FRONTEND_URL,
// email: {
// smtp: {
// host: envVars?.BREVO_SMTP_HOST,
// port: envVars?.BREVO_SMTP_PORT,
// secure: envVars?.BREVO_SMTP_PORT == 465, // true for 465, false for other ports
// auth: {
// user: envVars?.BREVO_SMTP_USER,
// pass: envVars?.BREVO_SMTP_PASS,
// },
// },
// from: envVars?.BREVO_FROM_EMAIL,
// api_key: envVars?.BREVO_EMAIL_API_KEY,
// BrevobaseURL: envVars?.BREVO_API_BASEURL,
// },
// oneSignal: {
// appID: envVars.ONESIGNAL_APPID,
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
// },
// branchIO: {
// branchIOKey: envVars.BRANCH_IO_KEY,
// },
};
} catch (error: unknown) {
if (error instanceof yup.ValidationError) {
console.error('Validation Errors:', error.errors.join(', '));
} else {
console.error('Unexpected error during configuration validation:', error);
}
console.error(
'Server shut down due to incomplete environment variable configuration.'
);
process.exit(1); // Exit with error code 1
}
}
/**
* Created By : Angad Chauhan
* Created at : 31/1/25
* Use : For google login .env file global variable
*/
// export const googleConfig = {
// clientID: process.env.GOOGLE_CLIENT_ID!,
// clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
// callbackURL: process.env.GOOGLE_CALLBACK_URL!,
// };
// Validate and export configuration only if validation succeeds
const config = getConfig();
export default config;

82
src/main.ts Normal file
View File

@@ -0,0 +1,82 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';
import helmet from 'helmet';
import { AppModule } from './app/app.module';
import { writeFileSync } from 'fs';
import { join } from 'path';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
// ✅ Security Middlewares
app.use(helmet());
app.enableCors({
origin: [
'http://localhost:3000', // Local Swagger UI
'http://localhost:3006', // Local Frontend
'https://editor.swagger.io', // Swagger Editor
'https://klc-admin.wdiprojects.com',
'https://admin-uat.klc.betadelivery.com',
],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
credentials: true,
});
// ✅ Global prefix
const globalPrefix = configService.get('API_PREFIX') || 'api/v1';
app.setGlobalPrefix(globalPrefix);
// ✅ Logging middleware
app.use((req, _res, next) => {
console.log(`📝 ${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
});
// ✅ Global validation
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
// ✅ Swagger setup (only for local/dev)
if (configService.get('NODE_ENV') !== 'production') {
const swaggerConfig = new DocumentBuilder()
.setTitle('Minglar API')
.setDescription('NestJS Backend for Minglar with Lambda-ready endpoints')
.setVersion(configService.get('API_VERSION') || '1.0.0')
.addBearerAuth()
.addServer(`${process.env.SERVER_URL || 'http://localhost:3000'}/`, 'Local Server')
.build();
const document = SwaggerModule.createDocument(app, swaggerConfig);
SwaggerModule.setup('api/docs', app, document);
// Optionally write swagger.json for offline usage
const outputPath = join(process.cwd(), 'swagger.json');
writeFileSync(outputPath, JSON.stringify(document, null, 2), 'utf8');
console.log(`✅ Swagger JSON file generated at: ${outputPath}`);
}
// ✅ Start app (only for local or non-Lambda)
const port = configService.get('PORT') || 3000;
await app.listen(port);
console.log(`🚀 Server running at http://localhost:${port}`);
if (configService.get('NODE_ENV') !== 'production') {
console.log(`📚 Swagger docs: http://localhost:${port}/api/docs`);
}
}
// Only run bootstrap if not in AWS Lambda
if (!process.env.AWS_LAMBDA_FUNCTION_NAME) {
bootstrap();
}

View File

View File

@@ -0,0 +1,51 @@
// src/modules/host/dto/host.dto.ts
import { IsInt, IsOptional, IsString, IsBoolean, IsEmail } from 'class-validator';
export class CreateHostDto {
@IsString()
firstName: string;
@IsString()
lastName: string;
@IsEmail()
emailAddress: string;
@IsOptional()
@IsString()
isdCode?: string;
@IsOptional()
@IsString()
mobileNumber?: string;
@IsOptional()
@IsString()
userPasscode?: string;
@IsOptional()
@IsInt()
roleXid?: number;
@IsOptional()
@IsBoolean()
isActive?: boolean;
}
export class UpdateHostDto {
@IsOptional()
@IsString()
firstName?: string;
@IsOptional()
@IsString()
lastName?: string;
@IsOptional()
@IsEmail()
emailAddress?: string;
@IsOptional()
@IsBoolean()
isActive?: boolean;
}

View File

@@ -0,0 +1,68 @@
// src/modules/host/handler/host.handler.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from 'src/app.module';
import { HostService } from '../services/host.service';
import { APIGatewayProxyHandler } from 'aws-lambda';
import { CreateHostDto, UpdateHostDto } from '../dto/host.dto';
let app;
let hostService: HostService;
async function bootstrap() {
if (!app) {
const nestApp = await NestFactory.createApplicationContext(AppModule);
hostService = nestApp.get(HostService);
app = nestApp;
}
}
export const handler: APIGatewayProxyHandler = async (event) => {
await bootstrap();
const method = event.httpMethod;
const path = event.path;
const body = event.body ? JSON.parse(event.body) : {};
try {
if (method === 'POST' && path.endsWith('/host')) {
const host = await hostService.createHost(body as CreateHostDto);
return response(201, host);
}
if (method === 'GET' && path.endsWith('/host')) {
const hosts = await hostService.getAllHosts();
return response(200, hosts);
}
if (method === 'GET' && path.match(/\/host\/\d+$/)) {
const id = Number(path.split('/').pop());
const host = await hostService.getHostById(id);
return response(200, host);
}
if (method === 'PUT' && path.match(/\/host\/\d+$/)) {
const id = Number(path.split('/').pop());
const host = await hostService.updateHost(id, body as UpdateHostDto);
return response(200, host);
}
if (method === 'DELETE' && path.match(/\/host\/\d+$/)) {
const id = Number(path.split('/').pop());
const host = await hostService.deleteHost(id);
return response(200, { message: 'Host deleted successfully', host });
}
return response(404, { message: 'Not Found' });
} catch (error) {
console.error('Error:', error);
return response(500, { message: error.message || 'Internal Server Error' });
}
};
function response(statusCode: number, body: any) {
return {
statusCode,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
};
}

View File

@@ -0,0 +1,34 @@
// src/modules/host/services/host.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../../common/database/prisma.service';
import { CreateHostDto, UpdateHostDto } from '../dto/host.dto';
@Injectable()
export class HostService {
constructor(private prisma: PrismaService) {}
async createHost(data: CreateHostDto) {
return this.prisma.user.create({ data });
}
async getAllHosts() {
return this.prisma.user.findMany({ where: { roleXid: 3 } });
}
async getHostById(id: number) {
const host = await this.prisma.user.findUnique({ where: { id } });
if (!host || host.roleXid !== 3) throw new Error('Host not found');
return host;
}
async updateHost(id: number, data: UpdateHostDto) {
return this.prisma.user.update({
where: { id },
data,
});
}
async deleteHost(id: number) {
return this.prisma.user.delete({ where: { id } });
}
}