Prashikshak / API /src /controller /form.controller.ts
Abhisingh-18's picture
Initial commit: Prashikshak - disaster management training platform
9a92a42
Raw
History Blame Contribute Delete
16.3 kB
import { Request, Response } from 'express';
import Form from '../model/form.model';
import FormResponse from '../model/formResponse.model';
import Event from '../model/event.model';
import User from '../model/user.model';
import crypto from 'crypto';
import QRCode from 'qrcode';
import { logActivity } from '../util/activity.util';
import { ActivityAction, ActivityCategory } from '../model/userActivity.model';
// Generate unique short code for form
const generateShortCode = async (): Promise<string> => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let code: string;
let exists = true;
while (exists) {
code = 'FORM-';
for (let i = 0; i < 6; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length));
}
const result = await Form.exists({ shortCode: code });
exists = result !== null;
// exists = await Form.exists({ shortCode: code });
}
return code!;
};
// ==================== CREATE FORM ====================
export const createForm = async (req: Request, res: Response) => {
console.log('createForm controller called');
try {
const userId = req.userId;
console.log('User ID:', userId);
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Allow trainers, organizations, and admins
const allowedRoles = ['trainer', 'organization', 'admin'];
if (!allowedRoles.includes(user.role)) {
console.log(`User role ${user.role} not authorized`);
return res.status(403).json({ error: 'Unauthorized to create forms' });
}
// Check verification for trainers
if (user.role === 'trainer' && user.organizationVerificationStatus !== 'verified') {
console.log('Trainer not verified');
return res.status(403).json({ error: 'Only verified trainers can create forms' });
}
const {
title,
description,
eventId,
fields,
accessType,
restrictToAttendees,
oneResponsePerUser,
showResponseSummary,
allowEditing
} = req.body;
console.log('Request body:', req.body);
// Validate event if provided
if (eventId) {
const event = await Event.findById(eventId);
if (!event) {
return res.status(404).json({ error: 'Event not found' });
}
// Check if user is creator of event
if (event.createdBy.toString() !== userId) {
// Admins might be allowed to create forms for any event, but let's keep it strict for now unless requested
if (user.role !== 'admin') {
return res.status(403).json({ error: 'You can only create forms for your own events' });
}
}
}
// Determine organization
let organizationId = user.organization;
if (user.role === 'organization') {
organizationId = user._id;
}
// For admins, organizationId might be undefined, which is now allowed
// Generate unique identifiers
console.log('Generating short code...');
const shortCode = await generateShortCode();
console.log('Short code generated:', shortCode);
const shareToken = crypto.randomBytes(32).toString('hex');
// Create form
const form = new Form({
title,
description,
event: eventId || undefined,
organization: organizationId,
createdBy: userId,
fields,
accessType: accessType || 'verified_users',
restrictToAttendees: restrictToAttendees || false,
oneResponsePerUser: oneResponsePerUser !== false,
shortCode,
shareToken,
showResponseSummary: showResponseSummary || false,
allowEditing: allowEditing || false,
status: 'draft'
});
console.log('Saving form...');
await form.save();
console.log('Form saved.');
// Generate QR code
console.log('Generating QR code...');
const formUrl = `${process.env.FRONTEND_URL}/forms/${form._id}?token=${shareToken}`;
const qrCodeDataUrl = await QRCode.toDataURL(formUrl);
console.log('QR code generated.');
form.qrCode = qrCodeDataUrl;
await form.save();
// Log activity
const activityMetadata: any = {
formId: form._id
};
if (eventId && form.event) {
activityMetadata.eventId = form.event;
}
await logActivity({
userId: user._id, // Use user._id instead of userId to ensure it's defined
action: ActivityAction.FORM_CREATE,
category: ActivityCategory.FORM,
description: `Created new form: ${form.title}`,
metadata: activityMetadata,
success: true
});
res.status(201).json({
message: 'Form created successfully',
form: {
id: form._id,
title: form.title,
shortCode: form.shortCode,
shareUrl: formUrl,
qrCode: qrCodeDataUrl
}
});
} catch (error: any) {
console.error('Create form error:', error);
res.status(500).json({ error: 'Failed to create form' });
}
};
// ==================== UPDATE FORM ====================
export const updateForm = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const user = await User.findById(userId);
if (!user) return res.status(404).json({ error: 'User not found' });
const form = await Form.findById(formId);
if (!form) {
return res.status(404).json({ error: 'Form not found' });
}
// Check ownership or admin
if (form.createdBy.toString() !== userId && user.role !== 'admin') {
return res.status(403).json({ error: 'You can only update your own forms' });
}
const {
title,
description,
fields,
accessType,
restrictToAttendees,
oneResponsePerUser,
acceptingResponses,
showResponseSummary,
allowEditing,
status
} = req.body;
// Update fields
if (title) form.title = title;
if (description !== undefined) form.description = description;
if (fields) form.fields = fields;
if (accessType) form.accessType = accessType;
if (restrictToAttendees !== undefined) form.restrictToAttendees = restrictToAttendees;
if (oneResponsePerUser !== undefined) form.oneResponsePerUser = oneResponsePerUser;
if (acceptingResponses !== undefined) form.acceptingResponses = acceptingResponses;
if (showResponseSummary !== undefined) form.showResponseSummary = showResponseSummary;
if (allowEditing !== undefined) form.allowEditing = allowEditing;
if (status) form.status = status;
await form.save();
res.json({
message: 'Form updated successfully',
form
});
} catch (error: any) {
console.error('Update form error:', error);
res.status(500).json({ error: 'Failed to update form' });
}
};
// ==================== GET FORM DETAILS ====================
export const getFormDetails = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const { token } = req.query;
const form = await Form.findById(formId)
.populate('createdBy', 'username email')
.populate('organization', 'username')
.populate('event', 'title code startDate');
if (!form) {
return res.status(404).json({ error: 'Form not found' });
}
// Verify access token for non-creators
if (form.createdBy._id.toString() !== userId && token !== form.shareToken) {
return res.status(403).json({ error: 'Invalid access token' });
}
// Check if user already submitted (if oneResponsePerUser is true)
let hasSubmitted = false;
if (form.oneResponsePerUser && userId) {
const existingResponse = await FormResponse.findOne({
form: formId,
respondent: userId
});
hasSubmitted = !!existingResponse;
}
res.json({
form,
hasSubmitted,
canEdit: form.allowEditing && hasSubmitted
});
} catch (error: any) {
console.error('Get form details error:', error);
res.status(500).json({ error: 'Failed to fetch form details' });
}
};
// ==================== SUBMIT FORM RESPONSE ====================
export const submitFormResponse = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const { responses } = req.body;
const user = await User.findById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
const form = await Form.findById(formId).populate('event');
if (!form) {
return res.status(404).json({ error: 'Form not found' });
}
// Check if form is accepting responses
if (!form.acceptingResponses || form.status === 'closed') {
return res.status(400).json({ error: 'Form is not accepting responses' });
}
// Access control checks
if (form.accessType === 'verified_users') {
// Allow any logged in user (which authMiddleware ensures)
// If we want to restrict to "verified" users only (e.g. verified trainers/orgs), we can add checks here.
// But user said "all users should be allowed to fill forms".
// So we just rely on them being logged in.
}
// Check if restricted to event attendees
if (form.restrictToAttendees && form.event) {
const event = form.event as any;
const isAttendee = event.participants.some(
(p: any) => p.user.toString() === userId
);
if (!isAttendee) {
return res.status(403).json({ error: 'Only event attendees can submit this form' });
}
}
// Check if user already submitted (if oneResponsePerUser is true)
if (form.oneResponsePerUser) {
const existingResponse = await FormResponse.findOne({
form: formId,
respondent: userId
});
if (existingResponse) {
if (!form.allowEditing) {
return res.status(400).json({ error: 'You have already submitted this form' });
}
// Update existing response
existingResponse.responses = responses;
existingResponse.lastEditedAt = new Date();
await existingResponse.save();
return res.json({
message: 'Response updated successfully',
response: existingResponse
});
}
}
// Validate required fields
const requiredFields = form.fields.filter(f => f.required);
const responseFieldIds = responses.map((r: any) => r.fieldId);
for (const field of requiredFields) {
if (!responseFieldIds.includes(field.id)) {
return res.status(400).json({
error: `Field "${field.label}" is required`
});
}
}
// Create new response
const formResponse = new FormResponse({
form: formId,
event: form.event || undefined,
respondent: userId,
responses,
ipAddress: req.ip,
userAgent: req.get('user-agent'),
submittedAt: new Date(),
isComplete: true
});
await formResponse.save();
// Increment response count
form.responseCount += 1;
await form.save();
res.status(201).json({
message: 'Response submitted successfully',
response: formResponse
});
} catch (error: any) {
console.error('Submit form response error:', error);
// Handle duplicate response error
if (error.code === 11000) {
return res.status(400).json({ error: 'You have already submitted this form' });
}
res.status(500).json({ error: 'Failed to submit response' });
}
};
// ==================== GET FORM RESPONSES ====================
export const getFormResponses = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const form = await Form.findById(formId);
if (!form) {
return res.status(404).json({ error: 'Form not found' });
}
// Only form creator can view responses
if (form.createdBy.toString() !== userId) {
return res.status(403).json({ error: 'You can only view responses to your own forms' });
}
const responses = await FormResponse.find({ form: formId })
.populate('respondent', 'username email role')
.sort({ submittedAt: -1 });
res.json({
form: {
title: form.title,
responseCount: form.responseCount
},
responses
});
} catch (error: any) {
console.error('Get form responses error:', error);
res.status(500).json({ error: 'Failed to fetch responses' });
}
};
// ==================== GET MY FORMS ====================
export const getMyForms = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { eventId } = req.query;
const query: any = { createdBy: userId };
if (eventId) {
query.event = eventId;
}
const forms = await Form.find(query)
.populate('event', 'title code')
.sort({ createdAt: -1 });
res.json({ forms });
} catch (error: any) {
console.error('Get my forms error:', error);
res.status(500).json({ error: 'Failed to fetch forms' });
}
};
// ==================== DELETE FORM ====================
export const deleteForm = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const user = await User.findById(userId);
if (!user) return res.status(404).json({ error: 'User not found' });
const form = await Form.findById(formId);
if (!form) {
return res.status(404).json({ error: 'Form not found' });
}
// Check ownership or admin
if (form.createdBy.toString() !== userId && user.role !== 'admin') {
return res.status(403).json({ error: 'You can only delete your own forms' });
}
// Delete all responses
await FormResponse.deleteMany({ form: formId });
// Delete form
await form.deleteOne();
res.json({ message: 'Form deleted successfully' });
} catch (error: any) {
console.error('Delete form error:', error);
res.status(500).json({ error: 'Failed to delete form' });
}
};
// ==================== GET MY RESPONSE ====================
export const getMyResponse = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const response = await FormResponse.findOne({
form: formId,
respondent: userId
}).populate('form', 'title fields allowEditing');
if (!response) {
return res.status(404).json({ error: 'No response found' });
}
res.json({ response });
} catch (error: any) {
console.error('Get my response error:', error);
res.status(500).json({ error: 'Failed to fetch response' });
}
};
// ==================== EXPORT RESPONSES TO CSV ====================
export const exportResponsesToCSV = async (req: Request, res: Response) => {
try {
const userId = req.userId;
const { formId } = req.params;
const form = await Form.findById(formId);
if (!form) {
return res.status(404).json({ error: 'Form not found' });
}
// Only form creator can export
if (form.createdBy.toString() !== userId) {
return res.status(403).json({ error: 'Unauthorized' });
}
const responses = await FormResponse.find({ form: formId })
.populate('respondent', 'username email')
.sort({ submittedAt: 1 });
// Build CSV
const headers = ['Timestamp', 'Respondent', 'Email'];
form.fields.forEach(field => headers.push(field.label));
const rows = responses.map(response => {
const row = [
new Date(response.submittedAt).toISOString(),
(response.respondent as any).username,
(response.respondent as any).email
];
form.fields.forEach(field => {
const fieldResponse = response.responses.find(r => r.fieldId === field.id);
row.push(fieldResponse ? JSON.stringify(fieldResponse.value) : '');
});
return row;
});
const csv = [headers, ...rows].map(row => row.join(',')).join('\n');
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="${form.title}-responses.csv"`);
res.send(csv);
} catch (error: any) {
console.error('Export CSV error:', error);
res.status(500).json({ error: 'Failed to export responses' });
}
};