Templates Controller
📖 Overview
Controllers/templates.js powers the reusable template catalogue surfaced in the Forms builder. It allows DashClicks admins to curate starter forms, exposes paginated listings for agency teams, and converts a template into an editable draft. Template authoring is tightly gated by scope (dashclicks.forms.templates) so only HQ teams can mutate shared assets, while agencies may browse/apply templates through the standard forms.templates scope.
🗄️ Collections touched
| Collection | Purpose |
|---|---|
forms.templates | Stores reusable template JSON, category/tag references, and onboarding flags. |
forms | Receives a new status: 'in_progress' form when useTemplate is invoked. |
🔐 Middleware & validation
Routes/templates.js applies the shared stack (verifyAuthorization, verifyAccessAndStatus, validateLimit, validateRequestSchemaV2(validators/templates)) with two notable scope splits:
dashclicks.forms.templates– required forPOST /forms/templatesandPUT /forms/templates/:templateid; reserved for internal staff.forms.templates(optionally withformsparent scope) – grants template listing, category listing, andPOST /forms/templates/usetemplate.
validators/templates.js ensures:
- Path params are valid ObjectIds.
- Bodies include a
templatedataobject (shape left open for flexibility). useTemplatepayload includes a validtemplate_id.
🛣️ Route map
| Method | Path (internal service) | Controller method | Notes |
|---|---|---|---|
POST | /forms/templates | saveTemplates | Admin-only creation of a template record. |
PUT | /forms/templates/:templateid | updateTemplates | Admin-only update of template metadata/content. |
POST | /forms/templates/usetemplate | useTemplate | Copies a template into forms and returns the new draft. |
GET | /forms/templates | getTemplates | Paginates through available templates with optional filters. |
GET | /forms/templates/categories | getCategories | Aggregates templates grouped by category (with optional search). |
GET | /forms/templates/:templateid | getTemplateById | Fetches a single template document. |
✨ Controller behaviour
saveTemplates
- Expects
req.body.templatedataand persists it directly toforms.templates. - Returns the saved document (Mongoose
save()result converted to lean object). - The caller is responsible for setting category/tag IDs,
is_onboarding_dc, and other metadata.
updateTemplates
- Path-param
:templateidmust be a valid ObjectId; the controller builds a lightweight condition{ _id: templateid }and callsupdateTemplatein the template model. - Returns the raw result of
findOneAndUpdate, so the UI sees the latest object immediately.
useTemplate
- Accepts
{ templatedata: { template_id } }. - Loads the source template and strips database-specific fields (
_id, timestamps,__v). - Forces
status: 'in_progress'to avoid publishing on initial clone. - Delegates to
formsModel.saveFormand returns the newly minted form document. - Returns
406if the template lookup fails, signalling the front-end to refresh its catalogue.
getTemplates
- Supports query params:
page,limit,categories,tags, andsearch. - Applies pagination with
skip = (page - 1) * limit. - If
req.auth.account.mainis falsy (i.e., the requester is a sub-account), the controller hides internal onboarding templates:- Excludes documents with
is_onboarding_dc: true. - Filters out names containing
(DC),InstaSites,InstaReports, etc., via regex.
- Excludes documents with
- Category/tag filters accept comma-separated lists and coerce each entry to an ObjectId before matching (
$in). - Delegates to
templatesModel.getAllTemplates, wrapping the result with shared pagination metadata viautilities.generatePagination.
getCategories
- Shares pagination plumbing with
getTemplates. - Optional
categoriesfilter accepts a comma-separated list (string matching rather than ObjectId). - Calls
templatesModel.getAllCategories, which groups templates by category and returns{ data, count }for the paginator. - The controller leaves search filtering to the model (search term passed through unmodified).
getTemplateById
- Simple wrapper around
templatesModel.findTemplate({ _id }). - Returns the first entry when an array is produced, otherwise an empty object.
⚠️ Notes & edge cases
- Scope enforcement – applying templates only requires
forms.templates, but creating or updating templates requires the dedicated admin scope. Ensure role assignments mirror this split to avoid exposing internal catalogues. - Sub-account catalogue parity – sub-accounts cannot see onboarding templates (
is_onboarding_dc) or ones containing certain keywords. If new onboarding templates are introduced, remember to update the regex or add flags appropriately. - Template hygiene –
useTemplatedoes not clonecreatedAt/updatedAtor_id, preventing collisions. It also ensures the resulting form starts asin_progressso the builder can finish configuration before publishing.
🔗 Related docs
- Forms controller – for CRUD, categories, send requests, and widget metrics.
- User response controller – handles submissions created from template-generated forms.