diff --git a/src/controllers/organization.controller.js b/src/controllers/organization.controller.js index 70525ab..9b751e8 100644 --- a/src/controllers/organization.controller.js +++ b/src/controllers/organization.controller.js @@ -1,7 +1,10 @@ import prisma from '../config/prismaClient.js'; import { sendEmail } from '../utils/email.utils.js'; -import { generateOTP, hashOTP } from '../utils/otp.utils.js'; -import { createOrganizationValidation } from '../validations/organization.validation.js'; +import { generateOTP, hashOTP, validateOTP } from '../utils/otp.utils.js'; +import { + createOrganizationValidation, + verifyOrganizationValidation, +} from '../validations/organization.validation.js'; /** * @swagger @@ -112,7 +115,7 @@ export const createOrganization = async (req, res, next) => { await sendEmail({ to: contactEmail, subject: 'Verify Your Organization Email', - text: `Organization name: ${result.org.name}\nYour verification code is: ${verificationOTP}. will expired in 10 min`, + text: `Organization name: ${result.org.name}\nYour verification code is: ${verificationOTP}. will expire in 10 min`, }); } catch (error) { next(error); @@ -121,7 +124,7 @@ export const createOrganization = async (req, res, next) => { return res.status(201).json({ success: true, - message: 'Organization created successfully', + message: `Organization created successfully. ${!isAdminCreation ? 'Please verify your org' : ''}`, data: { organization: { id: result.org.id, @@ -138,3 +141,47 @@ export const createOrganization = async (req, res, next) => { next(error); } }; + +export const verifyOrganization = async (req, res, next) => { + try { + const { error } = verifyOrganizationValidation(req.body); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + + const { email, otp } = req.body; + + const org = await prisma.organization.findFirst({ + where: { contactEmail: email }, // TODO: make email must entered in prisma and unique + }); + + if (!org) { + return res.status(404).json({ message: 'Organization not found' }); + } + + // Check OTP + if ( + !(await validateOTP(otp, org.emailVerificationOTP)) || + org.emailVerificationExpires < new Date() + ) { + return res.status(400).json({ message: 'Invalid or expired OTP' }); + } + + // Activate user and clear verification tokens + await prisma.organization.update({ + where: { id: org.id }, + data: { + isVerified: true, + status: 'APPROVAL', + emailVerificationOTP: null, + emailVerificationExpires: null, + }, + }); + + return res + .status(200) + .json({ message: 'Organization verified successfully' }); + } catch (error) { + next(error); + } +}; diff --git a/src/routes/organization.routes.js b/src/routes/organization.routes.js index 6e633d4..c192357 100644 --- a/src/routes/organization.routes.js +++ b/src/routes/organization.routes.js @@ -1,5 +1,8 @@ import { Router } from 'express'; -import { createOrganization } from '../controllers/organization.controller.js'; +import { + createOrganization, + verifyOrganization, +} from '../controllers/organization.controller.js'; import { verifyAccessToken } from '../middlewares/auth.middleware.js'; const router = Router(); @@ -88,4 +91,53 @@ const router = Router(); */ router.post('/api/organization', verifyAccessToken, createOrganization); +/** + * @swagger + * /api/organization/verifyOrg: + * post: + * summary: Verify an organization's email using OTP + * tags: [Organization] + * security: + * - bearerAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * - otp + * properties: + * email: + * type: string + * format: email + * description: Organization's registered contact email + * otp: + * type: string + * description: The OTP sent to the organization's email + * responses: + * 200: + * description: Organization verified successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: Organization verified successfully + * 400: + * description: Invalid or expired OTP + * 404: + * description: Organization not found + * 500: + * description: Server error + */ +router.post( + '/api/organization/verifyOrg', + verifyAccessToken, + verifyOrganization, +); + export default router; diff --git a/src/validations/organization.validation.js b/src/validations/organization.validation.js index be7afeb..c5dcb81 100644 --- a/src/validations/organization.validation.js +++ b/src/validations/organization.validation.js @@ -70,3 +70,20 @@ export const createOrganizationValidation = (obj) => { return schema.validate(obj); }; + +export const verifyOrganizationValidation = (obj) => { + const schema = Joi.object({ + email: Joi.string() + .email({ tlds: { allow: false } }) + .required() + .messages({ + 'string.email': 'Contact email must be a valid email address', + 'string.empty': 'Contact email is required', + }), + otp: Joi.string().required().trim().messages({ + 'string.empty': 'OTP is required.', + }), + }); + + return schema.validate(obj); +};