diff --git a/serverless.yml b/serverless.yml index 9ba7a5b..a18a2ca 100644 --- a/serverless.yml +++ b/serverless.yml @@ -166,6 +166,67 @@ functions: path: /host/add-payment-details method: post + getHostById: + handler: src/modules/host/handlers/getbyidhandler.handler + package: + patterns: + - 'src/modules/host/**' + - 'common/**' + - 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node' + - 'node_modules/@prisma/client/**' + - 'prisma/schema.prisma' + events: + - httpApi: + path: /host/{id} + method: get + + # 👇 Minglar Admin Module + + minglarRegistration: + handler: src/modules/minglaradmin/handlers/registration.handler + package: + patterns: + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node' + - 'node_modules/@prisma/client/**' + - 'node_modules/@aws-sdk/**' + - 'prisma/schema.prisma' + events: + - httpApi: + path: /minglaradmin/registration + method: post + + minglarLoginForAdmin: + handler: src/modules/minglaradmin/handlers/loginForMinglar.handler + package: + patterns: + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node' + - 'node_modules/@prisma/client/**' + - 'node_modules/@aws-sdk/**' + - 'prisma/schema.prisma' + events: + - httpApi: + path: /minglaradmin/login + method: post + + minglarCreatePassword: + handler: src/modules/minglaradmin/handlers/createPassword.handler + package: + patterns: + - 'src/modules/minglaradmin/**' + - 'common/**' + - 'node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node' + - 'node_modules/@prisma/client/**' + - 'node_modules/@aws-sdk/**' + - 'prisma/schema.prisma' + events: + - httpApi: + path: /minglaradmin/create-password + method: post + addCompanyDetails: handler: src/modules/minglaradmin/handlers/addCompanyDetails.handler package: diff --git a/src/modules/host/handlers/getbyidhandler.ts b/src/modules/host/handlers/getbyidhandler.ts new file mode 100644 index 0000000..6ab2163 --- /dev/null +++ b/src/modules/host/handlers/getbyidhandler.ts @@ -0,0 +1,50 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { HostService } from '../services/host.service'; +import ApiError from '../../../common/utils/helper/ApiError'; +import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; + +const prismaService = new PrismaService(); +const hostService = new HostService(prismaService); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context +): Promise => { + // Get host ID from path parameters + const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] + if(!token) { + throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); + } + + const userInfo = await verifyHostToken(token); + const id = Number(userInfo.id) + + if (!id) { + throw new ApiError(400, 'Host ID is required'); + } + + if (isNaN(id)) { + throw new ApiError(400, 'Invalid host ID format'); + } + + const hostDetails = await hostService.getHostById(id); + + if (!hostDetails) { + throw new ApiError(404, 'Host not found'); + } + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Host details retrieved successfully', + data: hostDetails, + }), + }; +}); diff --git a/src/modules/host/handlers/registration.ts b/src/modules/host/handlers/registration.ts index 5224a29..67790cb 100644 --- a/src/modules/host/handlers/registration.ts +++ b/src/modules/host/handlers/registration.ts @@ -44,7 +44,7 @@ export const handler = safeHandler(async ( newUser = user; } else { // ✅ No user found → create new one - newUser = await hostService.createHostUser(email); + newUser = await hostService.createMinglarUser(email); } const otpResult = await generateOtpHelper( diff --git a/src/modules/host/services/host.service.ts b/src/modules/host/services/host.service.ts index 83acc5a..8a41e73 100644 --- a/src/modules/host/services/host.service.ts +++ b/src/modules/host/services/host.service.ts @@ -19,10 +19,71 @@ export class HostService { } async getHostById(id: number) { - const host = await this.prisma.user.findUnique({ where: { id } }); + const host = await this.prisma.user.findUnique({ + where: { id }, + include: { + HostHeader: { + select: { + id: true, + companyName: true, + hostRefNumber: true, + address1: true, + address2: true, + cityXid: true, + stateXid: true, + countryXid: true, + pinCode: true, + logoPath: true, + isSubsidairy: true, + registrationNumber: true, + panNumber: true, + gstNumber: true, + formationDate: true, + companyType: true, + websiteUrl: true, + instagramUrl: true, + facebookUrl: true, + linkedinUrl: true, + twitterUrl: true, + currencyXid: true, + stepper: true, + hostStatusInternal: true, + hostStatusDisplay: true, + adminStatusInternal: true, + adminStatusDisplay: true, + amStatus: true, + agreementAccepted: true, + accountManagerXid: true, + isApproved: true, + agreementStartDate: true, + durationNumber: true, + durationFrequency: true, + isCommisionBase: true, + commisionPer: true, + amountPerBooking: true, + isActive: true, + createdAt: true, + updatedAt: true, + deletedAt: true, + currencies: true, + cities: true, + states: true, + countries: true, + HostBankDetails: true, + HostDocuments: true, + HostSuggestion: true, + hostParent: true, + HostTrack: true, + Activities: true, + }, + }, + }, + }); + if (!host || host.roleXid !== 4) { throw new ApiError(404, 'Host not found'); } + return host; } @@ -108,9 +169,9 @@ export class HostService { return existingUser; } - async createHostUser(email: string) { + async createMinglarUser(email: string) { const newUser = await this.prisma.user.create({ - data: { emailAddress: email, roleXid: 4 }, + data: { emailAddress: email, roleXid: 1 }, }); return newUser; } diff --git a/src/modules/minglaradmin/dto/minglar.dto.ts b/src/modules/minglaradmin/dto/minglar.dto.ts new file mode 100644 index 0000000..35b75ec --- /dev/null +++ b/src/modules/minglaradmin/dto/minglar.dto.ts @@ -0,0 +1,93 @@ +// src/modules/host/dto/host.dto.ts +import { IsInt, IsOptional, IsString, IsBoolean, IsEmail } from 'class-validator'; + +export class CreateMinglarDto { + @IsString() + firstName: string; + + @IsString() + lastName: string; + + @IsEmail() + emailAddress: string; + + @IsOptional() + @IsString() + isdCode?: string; + + @IsOptional() + @IsString() + mobileNumber?: string; + + @IsOptional() + @IsString() + userPassword?: string; + + @IsOptional() + @IsInt() + roleXid?: number; + + @IsOptional() + @IsBoolean() + isActive?: boolean; +} + +export class UpdateMinglarDto { + @IsOptional() + @IsString() + firstName?: string; + + @IsOptional() + @IsString() + lastName?: string; + + @IsOptional() + @IsEmail() + emailAddress?: string; + + @IsOptional() + @IsBoolean() + isActive?: boolean; +} + +export class GetMinglarLoginResponseDTO { + id: number; + firstName: string | null; + lastName: string | null; + emailAddress: string; + mobileNumber: string | null; + isActive: boolean; + roleXid: number; + accessToken: string; + refreshToken: string; + + constructor(user: any, accessToken: string, refreshToken: string) { + this.id = user.id; + this.firstName = user.firstName; + this.lastName = user.lastName; + this.emailAddress = user.emailAddress; + this.mobileNumber = user.mobileNumber; + this.isActive = user.isActive; + this.roleXid = user.roleXid; + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } +} + +export class AddPaymentDetailsDTO { + bankXid: number; + bankBranchXid: number; + accountNumber: number; + accountHolderName: string; + ifscCode: string; + hostXid: number; + + constructor(bankXid: number, bankBranchXid: number, accountNumber: number, accountHolderName: string, ifscCode: string, hostXid: number) { + this.bankXid = bankXid; + this.bankBranchXid = bankBranchXid; + this.accountNumber = accountNumber; + this.accountHolderName = accountHolderName; + this.ifscCode = ifscCode; + this.hostXid = hostXid; + } +} \ No newline at end of file diff --git a/src/modules/minglaradmin/handlers/loginForHost.ts b/src/modules/minglaradmin/handlers/loginForHost.ts deleted file mode 100644 index 1ee191a..0000000 --- a/src/modules/minglaradmin/handlers/loginForHost.ts +++ /dev/null @@ -1,70 +0,0 @@ -// import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; -// import { safeHandler } from '../../../common/utils/handlers/safeHandler'; -// import { PrismaService } from '../../../common/database/prisma.service'; -// import { HostService } from '../services/host.service'; -// import { TokenService } from '../services/token.service'; -// import { GetHostLoginResponseDTO } from '../dto/host.dto'; -// import ApiError from '../../../common/utils/helper/ApiError'; -// import * as bcrypt from 'bcryptjs'; - -// const prismaService = new PrismaService(); -// const hostService = new HostService(prismaService); -// const tokenService = new TokenService(); - -// export const handler = safeHandler(async ( -// event: APIGatewayProxyEvent, -// context?: Context -// ): Promise => { -// // Parse request body -// let body: { emailAddress?: string; userPassword?: string }; - -// try { -// body = event.body ? JSON.parse(event.body) : {}; -// } catch (error) { -// throw new ApiError(400, 'Invalid JSON in request body'); -// } - -// const { emailAddress } = body; - -// if (!emailAddress) { -// throw new ApiError(400, 'Email is required'); -// } - -// const loginForHost = await hostService.loginForHost(emailAddress); - -// if (!loginForHost) { -// throw new ApiError(400, 'Failed to login'); -// } - -// if (!matchPassword) { -// throw new ApiError(401, 'Invalid credentials'); -// } - -// const generateTokenForHost = await tokenService.generateAuthToken( -// loginForHost.id -// ); - -// if (!generateTokenForHost) { -// throw new ApiError(500, 'Failed to generate token'); -// } - -// const loginForHostResponse = new GetHostLoginResponseDTO( -// loginForHost, -// generateTokenForHost.access.token, -// generateTokenForHost.refresh.token -// ); - -// return { -// statusCode: 200, -// headers: { -// 'Content-Type': 'application/json', -// 'Access-Control-Allow-Origin': '*', -// }, -// body: JSON.stringify({ -// success: true, -// message: 'Login successful', -// data: loginForHostResponse, -// }), -// }; -// }); - diff --git a/src/modules/minglaradmin/handlers/loginForMinglar.ts b/src/modules/minglaradmin/handlers/loginForMinglar.ts new file mode 100644 index 0000000..ede215e --- /dev/null +++ b/src/modules/minglaradmin/handlers/loginForMinglar.ts @@ -0,0 +1,75 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import { PrismaService } from '../../../common/database/prisma.service'; +import { MinglarService } from '../services/minglar.service'; +import { TokenService } from "../services/token.service"; +import { GetMinglarLoginResponseDTO } from '../dto/minglar.dto'; +import ApiError from '../../../common/utils/helper/ApiError'; +import * as bcrypt from 'bcryptjs'; + +const prismaService = new PrismaService(); +const minglarSerivce = new MinglarService(prismaService); +const tokenService = new TokenService(); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context +): Promise => { + // Parse request body + let body: { emailAddress?: string; userPassword?: string }; + + try { + body = event.body ? JSON.parse(event.body) : {}; + } catch (error) { + throw new ApiError(400, 'Invalid JSON in request body'); + } + + const { emailAddress ,userPassword} = body; + + if (!emailAddress) { + throw new ApiError(400, 'Email is required'); + } + + const loginForMinglar = await minglarSerivce.loginForMinglar(emailAddress ,userPassword); + + if (!loginForMinglar) { + throw new ApiError(400, 'Failed to login'); + } + + const matchPassword = await bcrypt.compare( + userPassword, + loginForMinglar.userPassword + ); + + if (!matchPassword) { + throw new ApiError(401, 'Invalid credentials'); + } + + const generateTokenForHost = await tokenService.generateAuthToken( + loginForMinglar.id + ); + + if (!generateTokenForHost) { + throw new ApiError(500, 'Failed to generate token'); + } + + const loginForHostResponse = new GetMinglarLoginResponseDTO( + loginForMinglar, + generateTokenForHost.access.token, + generateTokenForHost.refresh.token + ); + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'Login successful', + data: loginForHostResponse, + }), + }; +}); + diff --git a/src/modules/minglaradmin/handlers/registration.ts b/src/modules/minglaradmin/handlers/registration.ts new file mode 100644 index 0000000..11aeaf5 --- /dev/null +++ b/src/modules/minglaradmin/handlers/registration.ts @@ -0,0 +1,75 @@ +import { MinglarService } from './../services/minglar.service'; +import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; +import { safeHandler } from '../../../common/utils/handlers/safeHandler'; +import { PrismaService } from '../../../common/database/prisma.service'; +import ApiError from '../../../common/utils/helper/ApiError'; +import * as bcrypt from 'bcryptjs'; +import { generateOtpHelper } from '../../../common/utils/helper/sendOtp'; + +const prismaService = new PrismaService(); +const minglarService = new MinglarService(prismaService); + +export const handler = safeHandler(async ( + event: APIGatewayProxyEvent, + context?: Context +): Promise => { + // Parse request body + let body: { email?: string }; + + try { + body = event.body ? JSON.parse(event.body) : {}; + } catch (error) { + throw new ApiError(400, 'Invalid JSON in request body'); + } + + const { email } = body; + + if (!email) { + throw new ApiError(400, 'Email is required'); + } + + const user = await prismaService.user.findUnique({ + where: { emailAddress: email }, + select: { emailAddress: true, id: true, userPassword: true }, + }); + + if (user && user.userPassword) { + throw new ApiError(404, 'User is already registered. Please login.'); + } + + let newUser; + + if (user && !user.userPassword) { + // ✅ User already exists but without password → reuse record + newUser = user; + } else { + // ✅ No user found → create new one + newUser = await minglarService.createHostUser(email); + } + + const otpResult = await generateOtpHelper( + Number(newUser?.id), + newUser?.emailAddress, + 'Register', + 6, + 5 + ); + + if (!otpResult || !otpResult.otp) { + throw new ApiError(500, 'Failed to send OTP'); + } + + return { + statusCode: 200, + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + success: true, + message: 'OTP sent successfully.', + data: {}, + }), + }; +}); + diff --git a/src/modules/minglaradmin/services/minglar.service.ts b/src/modules/minglaradmin/services/minglar.service.ts index b3c697b..7581a3a 100644 --- a/src/modules/minglaradmin/services/minglar.service.ts +++ b/src/modules/minglaradmin/services/minglar.service.ts @@ -4,6 +4,8 @@ import ApiError from '../../../common/utils/helper/ApiError'; import * as bcrypt from 'bcryptjs'; import { z } from 'zod'; import { hostCompanyDetailsSchema } from '../../../common/utils/validation/host/hostCompanyDetails.validation'; +import { CreateMinglarDto, UpdateMinglarDto } from '../dto/minglar.dto'; +import { User } from '@prisma/client'; type HostCompanyDetailsInput = z.infer; @@ -47,6 +49,134 @@ export class MinglarService { return true; } + async createHost(data: CreateMinglarDto) { + 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 }, + include: { + HostHeader: { + include: { + currencies: true, + cities: true, + states: true, + countries: true, + HostBankDetails: true, + HostDocuments: true, + HostSuggestion: true, + hostParent: true, + HostTrack: true, + Activities: true, + }, + }, + UserOtp: true, + Token: true, + }, + }); + + if (!host || host.roleXid !== 4) { + throw new ApiError(404, 'Host not found'); + } + + return host; + } + + async updateHost(id: number, data: UpdateMinglarDto) { + return this.prisma.user.update({ + where: { id }, + data, + }); + } + + async deleteHost(id: number) { + return this.prisma.user.delete({ where: { id } }); + } + + async getHostByEmail(email: string): Promise { + return this.prisma.user.findUnique({ where: { emailAddress: email } }); + } + + async verifyHostOtp(email: string, otp: string): Promise { + const user = await this.prisma.user.findUnique({ + where: { emailAddress: email }, + select: { + id: true, + emailAddress: true, + UserOtp: { + where: { isActive: true, isVerified: false }, + orderBy: { createdAt: 'desc' }, + take: 1, + }, + }, + }); + + if (!user) { + throw new ApiError(404, 'User not found.'); + } + + const userOtp = user.UserOtp[0]; + + if (!userOtp) { + throw new ApiError(400, 'No OTP found.'); + } + + if (new Date() > userOtp.expiresOn) { + throw new ApiError(400, 'OTP has expired.'); + } + + const isMatch = await bcrypt.compare(otp, userOtp.otpCode); + + if (!isMatch) { + throw new ApiError(400, 'Invalid OTP.'); + } + + await this.prisma.userOtp.update({ + where: { id: userOtp.id }, + data: { + isVerified: true, + verifiedOn: new Date(), + isActive: false, + }, + }); + + return true; + } + + async loginForMinglar(emailAddress: string, userPassword: string) { + const existingUser = await this.prisma.user.findUnique({ + where: { emailAddress: emailAddress }, + }); + + if (!existingUser) { + throw new ApiError(404, 'User not found'); + } + + if (existingUser.roleXid !== 4) { + throw new ApiError(403, 'Access denied. Not a host user.'); + } + + const matchPassword = await bcrypt.compare(userPassword, existingUser.userPassword); + if (!matchPassword) { + throw new ApiError(401, 'Invalid credentials'); + } + + return existingUser; + } + + async createHostUser(email: string) { + const newUser = await this.prisma.user.create({ + data: { emailAddress: email, roleXid: 4 }, + }); + return newUser; + } + + async addCompanyDetails( companyData: HostCompanyDetailsInput, documents: HostDocumentInput[] // Documents with S3 URLs diff --git a/src/modules/minglaradmin/services/token.service.ts b/src/modules/minglaradmin/services/token.service.ts new file mode 100644 index 0000000..50de3a7 --- /dev/null +++ b/src/modules/minglaradmin/services/token.service.ts @@ -0,0 +1,159 @@ +import { PrismaClient } from "@prisma/client"; +import jwt, { JwtPayload } from "jsonwebtoken"; +import moment from "moment"; +import config from "../../../config/config"; + +const prisma = new PrismaClient(); + +export class TokenService { + private generateToken( + user_xid: number, + expiresIn: Date, + type: string, + secret: string + ): { token: string; expires: Date } { + const token = jwt.sign( + { + sub: user_xid, + iat: moment().unix(), + exp: moment(expiresIn).unix(), + type, + }, + secret + ); + + return { token, expires: expiresIn }; + } + + async generateAuthToken( + user_xid: number, + ): Promise<{ + access: { token: string; expires: Date }; + refresh: { token: string; expires: Date }; + }> { + const accessTokenExpires = moment() + .add(config.jwt.accessExpirationMinutes, "minutes") + .toDate(); + + const refreshTokenExpires = moment() + .add(config.jwt.refreshExpirationDays, "days") + .toDate(); + + const accessToken = this.generateToken( + user_xid, + accessTokenExpires, + "access", + config.jwt.secret + ); + + const refreshToken = this.generateToken( + user_xid, + refreshTokenExpires, + "refresh", + config.jwt.secret + ); + + await prisma.token.create({ + data: { + token: refreshToken.token, + expiringAt: refreshToken.expires, + tokenType: "refresh", + isBlackListed: false, + + user: { + connect: { id: user_xid }, + }, + }, + }); + + return { + access: accessToken, + refresh: refreshToken, + }; + } + + async generateAuthTokenAdmin( + user_xid: number + ): Promise<{ + access: { token: string; expires: Date }; + refresh: { token: string; expires: Date }; + }> { + const accessTokenExpires = moment() + .add(config.jwt.accessExpirationMinutes, "minutes") + .toDate(); + + const refreshTokenExpires = moment() + .add(config.jwt.refreshExpirationDays, "days") + .toDate(); + + const accessToken = this.generateToken( + user_xid, + accessTokenExpires, + "access", + config.jwt.secret + ); + + const refreshToken = this.generateToken( + user_xid, + refreshTokenExpires, + "refresh", + config.jwt.secret + ); + + await prisma.token.create({ + data: { + token: refreshToken.token, + expiringAt: refreshToken.expires, + tokenType: "refresh", + isBlackListed: false, + user: { + connect: { id: user_xid }, + }, + }, + }); + + return { + access: accessToken, + refresh: refreshToken, + }; + } + + async revokeToken(user_xid: number, deviceId: string): Promise { + const existingToken = await prisma.token.findFirst({ + where: { + id: user_xid, + deviceId, + }, + }); + + if (!existingToken) return false; + + await prisma.token.delete({ where: { id: existingToken.id } }); + return true; + } + + async isTokenBlackListed(token: string): Promise { + const existing = await prisma.token.findUnique({ + where: { token }, + }); + return existing ? true : false; + } + + async verifyRefreshToken( + token: string + ): Promise { + try { + return jwt.verify(token, config.jwt.secret); + } catch { + return null; + } + } + + async decodeToken(token: string): Promise { + try { + return jwt.decode(token); + } catch { + return null; + } + } +} diff --git a/swagger.json b/swagger.json index 300e5db..476e7a6 100644 --- a/swagger.json +++ b/swagger.json @@ -1,631 +1,27 @@ { "openapi": "3.0.0", + "paths": {}, "info": { "title": "Minglar API", - "description": "NestJS Backend for Minglar with AWS Lambda endpoints", + "description": "NestJS Backend for Minglar with Lambda-ready endpoints", "version": "1.0.0", - "contact": { - "name": "API Support" - } + "contact": {} }, + "tags": [], "servers": [ { - "url": "https://api.minglar.com", - "description": "Production Server" - }, - { - "url": "http://localhost:3000", - "description": "Local Development Server" + "url": "http://localhost:3000/", + "description": "Local Server" } ], - "tags": [ - { - "name": "Host", - "description": "Host management endpoints" - }, - { - "name": "Authentication", - "description": "Authentication and authorization endpoints" - } - ], - "paths": { - "/host": { - "get": { - "tags": ["Host"], - "summary": "Get all hosts", - "description": "Retrieves a list of all host headers with basic information", - "operationId": "getHosts", - "responses": { - "200": { - "description": "Successful response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/HostHeader" - } - }, - "example": [ - { - "hostParent": "Example Host", - "hostRefNumber": "HOST001", - "hostStatusDisplay": "Active", - "accountManager": "John Doe" - } - ] - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/host/registration": { - "post": { - "tags": ["Authentication"], - "summary": "Register a new host", - "description": "Initiates host registration by sending an OTP to the provided email address", - "operationId": "registrationOfHost", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RegistrationRequest" - }, - "example": { - "email": "host@example.com" - } - } - } - }, - "responses": { - "200": { - "description": "OTP sent successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - }, - "example": { - "success": true, - "message": "OTP sent successfully.", - "data": {} - } - } - } - }, - "400": { - "description": "Bad request - Email is required", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "Email is required", - "data": null, - "error": { - "code": 400, - "description": "Email is required", - "statusCode": 400 - } - } - } - } - }, - "404": { - "description": "User already registered", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "User is already registered. Please login.", - "data": null, - "error": { - "code": 404, - "description": "User is already registered. Please login.", - "statusCode": 404 - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/host/verify-otp": { - "post": { - "tags": ["Authentication"], - "summary": "Verify OTP", - "description": "Verifies the OTP sent to the user's email during registration", - "operationId": "verifyOtp", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VerifyOtpRequest" - }, - "example": { - "email": "host@example.com", - "otp": "123456" - } - } - } - }, - "responses": { - "200": { - "description": "OTP verified successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - }, - "example": { - "success": true, - "message": "OTP verified successfully", - "data": null - } - } - } - }, - "400": { - "description": "Bad request - Invalid OTP or missing fields", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "examples": { - "missingFields": { - "value": { - "success": false, - "message": "Email and OTP are required", - "data": null, - "error": { - "code": 400, - "description": "Email and OTP are required", - "statusCode": 400 - } - } - }, - "invalidOtp": { - "value": { - "success": false, - "message": "Invalid OTP.", - "data": null, - "error": { - "code": 400, - "description": "Invalid OTP.", - "statusCode": 400 - } - } - }, - "expiredOtp": { - "value": { - "success": false, - "message": "OTP has expired.", - "data": null, - "error": { - "code": 400, - "description": "OTP has expired.", - "statusCode": 400 - } - } - } - } - } - } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "User not found.", - "data": null, - "error": { - "code": 404, - "description": "User not found.", - "statusCode": 404 - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/host/login": { - "post": { - "tags": ["Authentication"], - "summary": "Login for host", - "description": "Authenticates a host user and returns an access token", - "operationId": "loginForHost", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LoginRequest" - }, - "example": { - "emailAddress": "host@example.com", - "userPassword": "password123" - } - } - } - }, - "responses": { - "200": { - "description": "Login successful", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/LoginResponse" - }, - "example": { - "success": true, - "message": "Login successful", - "data": { - "id": 1, - "firstName": "John", - "lastName": "Doe", - "emailAddress": "host@example.com", - "mobileNumber": "+1234567890", - "isActive": true, - "roleXid": 4, - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - } - } - } - } - }, - "400": { - "description": "Bad request - Missing fields or failed login", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "Email and password are required", - "data": null, - "error": { - "code": 400, - "description": "Email and password are required", - "statusCode": 400 - } - } - } - } - }, - "401": { - "description": "Unauthorized - Invalid credentials", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "Invalid credentials", - "data": null, - "error": { - "code": 401, - "description": "Invalid credentials", - "statusCode": 401 - } - } - } - } - }, - "403": { - "description": "Forbidden - Not a host user", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "Access denied. Not a host user.", - "data": null, - "error": { - "code": 403, - "description": "Access denied. Not a host user.", - "statusCode": 403 - } - } - } - } - }, - "404": { - "description": "User not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - }, - "example": { - "success": false, - "message": "User not found", - "data": null, - "error": { - "code": 404, - "description": "User not found", - "statusCode": 404 - } - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - } - }, "components": { "securitySchemes": { - "bearerAuth": { - "type": "http", + "bearer": { "scheme": "bearer", "bearerFormat": "JWT", - "description": "JWT token obtained from login endpoint" + "type": "http" } }, - "schemas": { - "HostHeader": { - "type": "object", - "properties": { - "hostParent": { - "type": "string", - "description": "Host parent name", - "example": "Example Host" - }, - "hostRefNumber": { - "type": "string", - "description": "Host reference number", - "example": "HOST001" - }, - "hostStatusDisplay": { - "type": "string", - "description": "Host status display", - "example": "Active" - }, - "accountManager": { - "type": "string", - "description": "Account manager name", - "example": "John Doe" - } - } - }, - "RegistrationRequest": { - "type": "object", - "required": ["email"], - "properties": { - "email": { - "type": "string", - "format": "email", - "description": "Email address for registration", - "example": "host@example.com" - } - } - }, - "VerifyOtpRequest": { - "type": "object", - "required": ["email", "otp"], - "properties": { - "email": { - "type": "string", - "format": "email", - "description": "Email address used during registration", - "example": "host@example.com" - }, - "otp": { - "type": "string", - "description": "6-digit OTP code sent to email", - "pattern": "^[0-9]{6}$", - "example": "123456" - } - } - }, - "LoginRequest": { - "type": "object", - "required": ["emailAddress", "userPassword"], - "properties": { - "emailAddress": { - "type": "string", - "format": "email", - "description": "Host user email address", - "example": "host@example.com" - }, - "userPassword": { - "type": "string", - "format": "password", - "description": "User password", - "minLength": 8, - "example": "password123" - } - } - }, - "LoginResponseData": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "User ID", - "example": 1 - }, - "firstName": { - "type": "string", - "nullable": true, - "description": "User first name", - "example": "John" - }, - "lastName": { - "type": "string", - "nullable": true, - "description": "User last name", - "example": "Doe" - }, - "emailAddress": { - "type": "string", - "format": "email", - "description": "User email address", - "example": "host@example.com" - }, - "mobileNumber": { - "type": "string", - "nullable": true, - "description": "User mobile number", - "example": "+1234567890" - }, - "isActive": { - "type": "boolean", - "description": "User active status", - "example": true - }, - "roleXid": { - "type": "integer", - "description": "User role ID (4 for host)", - "example": 4 - }, - "accessToken": { - "type": "string", - "description": "JWT access token for authentication", - "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." - } - } - }, - "LoginResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Login successful" - }, - "data": { - "$ref": "#/components/schemas/LoginResponseData" - } - } - }, - "SuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": true - }, - "message": { - "type": "string", - "example": "Operation successful" - }, - "data": { - "type": "object", - "description": "Response data (can be null or empty object)" - } - } - }, - "ErrorResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "example": false - }, - "message": { - "type": "string", - "description": "Error message", - "example": "An error occurred" - }, - "data": { - "type": "object", - "nullable": true, - "example": null - }, - "error": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "description": "HTTP status code", - "example": 400 - }, - "description": { - "type": "string", - "description": "Error description", - "example": "Bad request" - }, - "statusCode": { - "type": "integer", - "description": "HTTP status code", - "example": 400 - }, - "debug": { - "type": "string", - "description": "Debug information (only in non-production environments)", - "example": "Stack trace..." - } - }, - "required": ["code", "description", "statusCode"] - } - }, - "required": ["success", "message", "data", "error"] - } - } + "schemas": {} } -} +} \ No newline at end of file