Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions src/controllers/organization.controller.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);
}
};
54 changes: 53 additions & 1 deletion src/routes/organization.routes.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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;
17 changes: 17 additions & 0 deletions src/validations/organization.validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};