const Joi = require('joi'); const controller = require('../controllers/form.controller'); const grantController = require('../controllers/formGrant.controller'); const { requireFormPermissionPre, requirePermissionPre } = require('../services/acl.service'); const { FORM_TYPES, FIELD_TYPES, FORM_FIELD_MAPS_TO } = require('../config/constants'); /** stripUnknown: Mongo subdocs include _id; duplicates must not fail validation. */ const fieldSchema = Joi.object({ label: Joi.string(), name: Joi.string().allow('', null), type: Joi.string().valid(...FIELD_TYPES), required: Joi.boolean(), options: Joi.array().items(Joi.string()), allowOther: Joi.boolean(), otherLabel: Joi.string().allow('', null), placeholder: Joi.string().allow('', null), remarks: Joi.string().allow('', null), uiType: Joi.string().allow('', null), mapsTo: Joi.string().valid(...FORM_FIELD_MAPS_TO).allow('', null), customMapsKey: Joi.string().allow('', null), isFixed: Joi.boolean(), convertToTag: Joi.boolean(), }).options({ stripUnknown: true }); const eventHeaderSchema = Joi.object({ showLogo: Joi.boolean(), showTitle: Joi.boolean(), showDateRange: Joi.boolean(), showEventType: Joi.boolean(), showLocationType: Joi.boolean(), showVenue: Joi.boolean(), }).options({ stripUnknown: true }); const settingsSchema = Joi.object({ isActive: Joi.boolean(), allowMultiple: Joi.boolean(), submissionLimit: Joi.number(), thankYouMessage: Joi.string().allow('', null), responseDeadline: Joi.alternatives().try(Joi.date(), Joi.string().allow('', null), Joi.valid(null)), showThankYou: Joi.boolean(), collectEmail: Joi.boolean(), allowAnonymous: Joi.boolean(), paymentEnabled: Joi.boolean(), paymentAmount: Joi.alternatives().try(Joi.string(), Joi.number()).allow('', null), sendConfirmationEmail: Joi.boolean(), confirmationEmailTemplateId: Joi.string().hex().length(24).allow(null).empty(''), onSubmitAddTags: Joi.array().items(Joi.string()), /** Empty string must become null — mongoose ObjectId rejects "". */ onSubmitParticipantListId: Joi.string().hex().length(24).allow(null).empty(''), allowDuplicateEmail: Joi.boolean(), allowDuplicatePhone: Joi.boolean(), formClosedMessage: Joi.string().allow('', null), submitErrorMessage: Joi.string().allow('', null), captchaFailedMessage: Joi.string().allow('', null), registrationClosedMessage: Joi.string().allow('', null), registrationDeadlineMessage: Joi.string().allow('', null), registrationFullMessage: Joi.string().allow('', null), duplicateEmailMessage: Joi.string().allow('', null), duplicatePhoneMessage: Joi.string().allow('', null), eventHeader: eventHeaderSchema, }).options({ stripUnknown: true }); const formPayload = Joi.object({ name: Joi.string().required(), type: Joi.string().valid(...FORM_TYPES).required(), description: Joi.string().allow('', null), fields: Joi.array().items(fieldSchema), settings: settingsSchema, defaultVisibility: Joi.string().valid('private', 'org'), }).options({ stripUnknown: true }); const formUpdatePayload = formPayload.keys({ event: Joi.string().hex().length(24).required(), }); /** tenantOrganization: super_admin may set explicitly; others get it from JWT in the controller. */ const formOrgCreatePayload = Joi.object({ name: Joi.string().required(), type: Joi.string().valid(...FORM_TYPES).required(), description: Joi.string().allow('', null), fields: Joi.array().items(fieldSchema), settings: settingsSchema, event: Joi.string().hex().length(24).required(), defaultVisibility: Joi.string().valid('private', 'org'), tenantOrganization: Joi.alternatives().try( Joi.string().hex().length(24), Joi.string().allow(null, ''), Joi.valid(null) ), }).options({ stripUnknown: true }); const submitPayload = Joi.object({ participantId: Joi.string(), data: Joi.object().pattern(Joi.string(), Joi.any()), captchaToken: Joi.string().allow('', null), captchaAnswer: Joi.alternatives().try(Joi.string(), Joi.number()).allow('', null), _gotcha: Joi.string().allow('', null), }); const replyPayload = Joi.object({ submissionId: Joi.string().required(), to: Joi.string().email().required(), subject: Joi.string().allow('', null), body: Joi.string().allow('', null), }); const grantPayload = Joi.object({ subjectType: Joi.string().valid('user', 'role').required(), subjectId: Joi.string().hex().length(24).required(), actions: Joi.array().items(Joi.string().valid('read', 'write', 'delete')), }); module.exports = [ { method: 'GET', path: '/api/forms', options: { pre: [{ method: requirePermissionPre('forms', 'read') }], handler: controller.listByOrganization }, }, { method: 'POST', path: '/api/forms', options: { validate: { payload: formOrgCreatePayload }, pre: [{ method: requirePermissionPre('forms', 'write') }], handler: controller.createByOrganization }, }, { method: 'GET', path: '/api/events/{eventId}/forms', options: { pre: [{ method: requirePermissionPre('forms', 'read') }], handler: controller.list }, }, { method: 'POST', path: '/api/events/{eventId}/forms', options: { validate: { payload: formPayload }, pre: [{ method: requirePermissionPre('forms', 'write') }], handler: controller.create }, }, { method: 'GET', path: '/api/forms/{id}', options: { pre: [{ method: requireFormPermissionPre('read') }], handler: controller.get }, }, { method: 'PUT', path: '/api/forms/{id}', options: { validate: { payload: formUpdatePayload }, pre: [{ method: requireFormPermissionPre('write') }], handler: controller.update }, }, { method: 'DELETE', path: '/api/forms/{id}', options: { pre: [{ method: requireFormPermissionPre('delete') }], handler: controller.remove }, }, { method: 'POST', path: '/api/forms/{formId}/submit', options: { auth: false, validate: { payload: submitPayload }, handler: controller.submit }, }, { method: 'GET', path: '/api/public/forms/{formId}', options: { auth: false, handler: controller.getPublicForm }, }, { method: 'GET', path: '/api/forms/{formId}/submissions', options: { pre: [{ method: requireFormPermissionPre('read', { idParam: 'formId' }) }], handler: controller.listSubmissions }, }, { method: 'POST', path: '/api/forms/{formId}/submissions/reply', options: { validate: { payload: replyPayload }, pre: [{ method: requireFormPermissionPre('read', { idParam: 'formId' }) }], handler: controller.replyToSubmission, }, }, { method: 'GET', path: '/api/forms/{formId}/grants', options: { pre: [{ method: requireFormPermissionPre('read', { idParam: 'formId' }) }], handler: grantController.listGrants }, }, { method: 'POST', path: '/api/forms/{formId}/grants', options: { validate: { payload: grantPayload }, pre: [{ method: requireFormPermissionPre('write', { idParam: 'formId' }) }], handler: grantController.createGrant, }, }, { method: 'DELETE', path: '/api/forms/{formId}/grants/{grantId}', options: { pre: [{ method: requireFormPermissionPre('write', { idParam: 'formId' }) }], handler: grantController.removeGrant }, }, { method: 'PUT', path: '/api/forms/{formId}/visibility', options: { validate: { payload: Joi.object({ defaultVisibility: Joi.string().valid('private', 'org').required() }) }, pre: [{ method: requireFormPermissionPre('write', { idParam: 'formId' }) }], handler: grantController.updateFormVisibility, }, }, ];