| 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'; |
|
|
| |
| 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; |
| |
| } |
|
|
| return code!; |
| }; |
|
|
| |
| 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' }); |
| } |
|
|
| |
| 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' }); |
| } |
|
|
| |
| 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); |
|
|
| |
| if (eventId) { |
| const event = await Event.findById(eventId); |
| if (!event) { |
| return res.status(404).json({ error: 'Event not found' }); |
| } |
|
|
| |
| if (event.createdBy.toString() !== userId) { |
| |
| if (user.role !== 'admin') { |
| return res.status(403).json({ error: 'You can only create forms for your own events' }); |
| } |
| } |
| } |
|
|
| |
| let organizationId = user.organization; |
| if (user.role === 'organization') { |
| organizationId = user._id; |
| } |
| |
|
|
| |
| console.log('Generating short code...'); |
| const shortCode = await generateShortCode(); |
| console.log('Short code generated:', shortCode); |
| const shareToken = crypto.randomBytes(32).toString('hex'); |
|
|
| |
| 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.'); |
|
|
| |
| 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(); |
|
|
| |
| const activityMetadata: any = { |
| formId: form._id |
| }; |
| if (eventId && form.event) { |
| activityMetadata.eventId = form.event; |
| } |
|
|
| await logActivity({ |
| userId: user._id, |
| 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' }); |
| } |
| }; |
|
|
| |
| 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' }); |
| } |
|
|
| |
| 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; |
|
|
| |
| 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' }); |
| } |
| }; |
|
|
| |
| 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' }); |
| } |
|
|
| |
| if (form.createdBy._id.toString() !== userId && token !== form.shareToken) { |
| return res.status(403).json({ error: 'Invalid access token' }); |
| } |
|
|
| |
| 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' }); |
| } |
| }; |
|
|
| |
| 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' }); |
| } |
|
|
| |
| if (!form.acceptingResponses || form.status === 'closed') { |
| return res.status(400).json({ error: 'Form is not accepting responses' }); |
| } |
|
|
| |
| if (form.accessType === 'verified_users') { |
| |
| |
| |
| |
| } |
|
|
| |
| 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' }); |
| } |
| } |
|
|
| |
| 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' }); |
| } |
|
|
| |
| existingResponse.responses = responses; |
| existingResponse.lastEditedAt = new Date(); |
| await existingResponse.save(); |
|
|
| return res.json({ |
| message: 'Response updated successfully', |
| response: existingResponse |
| }); |
| } |
| } |
|
|
| |
| 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` |
| }); |
| } |
| } |
|
|
| |
| 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(); |
|
|
| |
| 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); |
|
|
| |
| 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' }); |
| } |
| }; |
|
|
| |
| 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' }); |
| } |
|
|
| |
| 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' }); |
| } |
| }; |
|
|
| |
| 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' }); |
| } |
| }; |
|
|
| |
| 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' }); |
| } |
|
|
| |
| if (form.createdBy.toString() !== userId && user.role !== 'admin') { |
| return res.status(403).json({ error: 'You can only delete your own forms' }); |
| } |
|
|
| |
| await FormResponse.deleteMany({ form: formId }); |
|
|
| |
| 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' }); |
| } |
| }; |
|
|
| |
| 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 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' }); |
| } |
|
|
| |
| 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 }); |
|
|
| |
| 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' }); |
| } |
| }; |