Skip to main content

User Response Controller

πŸ“– Overview​

Controllers/userresponse.js receives every form submissionβ€”whether submitted publicly via share link or via a tracked requestβ€”and fans the data out to downstream systems. Beyond storing the raw payload, it updates invite statuses, kicks off onboarding automations (Instasites/Instareports builds, CRM contact creation, support conversations), updates Store orders, and exposes rich listings for agency dashboards (including cross-account views).

πŸ—„οΈ Collections touched​

CollectionPurpose
forms.userresponsePersists submission payloads, analytics metadata, and soft-delete flags.
forms.sent.requestsLookup + update request status, link responses to invites, compute pending queues.
formsResolves ownership and ensures delete permissions before mutating responses.
campaign.dataIndirect via the inbound utility (determines automation recipes).
crm.contactsHydrates recipient info, builds pending request payloads, and enriches in-bound submissions.
support.* collectionsCreated during inbound automation to open support rooms/messages for new leads.
_store.ordersUpdates onboarding status when a response matches a stored request.
funnels / funnel.analyticsWhen responses originate from funnel steps, analytics are logged.
queues, leads-data, instasites, instareportsAll triggered inside utilities/inbound.js for automation side effects.

πŸ” Middleware & validation​

Routes under /forms reuse the module validators (validators/index.js) with the following highlights:

  • Most list endpoints require forms.read (optionally forms.templates for template context) and use validateLimit for pagination bounds.
  • Sub-account accessors (/forms/:subaccountid/subaccountresponse etc.) verify the requested account is a child of the authenticated parent, returning RESOURCE_NOT_FOUND otherwise.
  • Public submissions (POST /forms/:formid/userresponse) run the Joi guard but skip authorization to keep share links open.
  • Update/delete paths ensure formid and responseid are valid ObjectIds.

πŸ›£οΈ Route map​

MethodPath (internal service)Controller methodNotes
POST/forms/:formid/userresponsesaveResponseAccepts public submissions or invite completions; orchestrates automation.
GET/forms/:formid/userresponsegetUserResponsesLists submissions (default) or invite-based type=requests; supports export mode.
PUT/forms/:formid/userresponse/:responseidupdateUserResponsePATCH-style update that wraps payload under data.
PUT/forms/:formid/formrequest/:requestidupdateFormRequestMutate invite metadata (status, reminder flags, etc.).
GET/forms/:formid/getpendinguserresponsegetPendingResponsesPending invites (status: 'sent') for a single form.
GET/forms/:subaccountid/subaccountresponsegetSubAccountsResponsesParent view across a child account's responses.
GET/forms/:subaccountid/subaccountpendingrequestsgetSubAccountPendingRequestsPending invite view for a child account.
GET/forms/mainaccount/responsesgetMainAccountsResponsesOwner-level roll-up of responses.
GET/forms/mainaccount/pendingrequestsgetmainAccountPendingRequestsPending invites across the main account.
GET/forms/:formid/userresponse/:responseidgetUserResponsesByIdFetches a single response (guarded by scope).
DELETE/forms/:formid/userresponse/:responseiddeleteUserResponseMarks a response is_deleted=true after owner check.
DELETE/forms/:formid/userresponsedeleteUserResponsesBulk soft-delete (comma-separated responses query param).

βœ‰οΈ Submission ingest (saveResponse)​

Two flows share the same method:

  1. Invite completion (rid + rqid present)

    • Validates the invite via forms.sent.requests. Rejects with descriptive errors if already submitted or cancelled.
    • Copies invite metadata onto the response (form_id, account_id, request_id) and updates the invite status to submitted.
    • Checks the form's owning account status via checkAccountStatus; returns a success envelope with a failure message if the account is inactive.
    • Persists the submission (userResponse.is_deleted=false by default).
    • Calls inbound.inHouseForms, which handles CRM contact upsert, lead creation, notifications, Instasite/Instareport builds, support room creation, and deal creation (depending on campaign config).
    • Updates StoreOrder onboarding milestones: when a request matches an onboarding order without a response, the order's status flips to received, and response/form IDs are recorded.
  2. Public submission (rid absent)

    • Finds the form via form_secret_id and stamps the plain response with type: 'public' plus the resolved form_id.
    • Performs the same account status check and inbound automation pipeline as above.

Common behaviour:

  • Accepts optional analytics payload to store visitor/session data alongside the response.
  • Returns { success: true, data: <response>, message: 'SUCCESS' } and appends contact_id from the inbound helper when a CRM record is created.

πŸ“Š Listings & exports​

getUserResponses​

  • Query params: page, limit, type, sort, order, export.
  • When type=requests, aggregates against forms.sent.requests, joining the linked response (status: 'submitted') and contact info. The result object contains submittedrequests plus a count; pagination still applies when limit > 0.
  • Default (type omitted) reads from forms.userresponse, filters out is_deleted=true, and returns { data: [...], total: [...] } from the aggregation facet.
  • export=true bypasses pagination (limit=0), strips Mongo metadata fields (_id, __v, timestamps, control flags), and delivers raw submission content.

getSubAccountsResponses / getMainAccountsResponses​

  • Parent view ensures the requested sub-account belongs to the current parent; otherwise returns RESOURCE_NOT_FOUND.
  • Aggregations enrich responses with contact + business details via $lookup into crm.contacts, include funnel metadata, and preserve pagination semantics.
  • Search applies a regex to forms.form_name.

Pending invite listings​

  • getSubAccountPendingRequests, getmainAccountPendingRequests, and getPendingResponses all delegate to UserResponseModel.getAllPendingResponses with slightly different conditions (recipient IDs vs account IDs vs form ID).
  • Each entry includes the form metadata and the recipient contact or user details.
  • Sub-account variant appends fully qualified onboarding links using utilities.getActiveDomain for the parent account.

πŸ”„ Mutations​

  • updateUserResponse wraps the provided updateData inside { data: { ... } } and runs updateMany, enabling partial updates to stored payload JSON.
  • updateFormRequest mutates invite records (e.g., setting status, adding reminder timestamps) while enforcing account ownership.
  • deleteUserResponse & deleteUserResponses perform soft deletes (is_deleted: true) after confirming the form belongs to the authenticated account.

⚠️ Edge cases & safeguards​

  • Account inactivity – Submissions are accepted but respond with status: false and a message informing the user the form is inactive when checkAccountStatus fails.
  • Invalid request IDs – saveResponse logs failures updating StoreOrder when requestId is malformed to avoid crashing the overall submission pipeline.
  • Duplicate contacts – Inbound helper performs extensive matching/merging to avoid duplicate CRM contacts; errors are recorded on the response document (errors array) for later inspection.
  • Bulk deletion – For bulk soft-delete, the controller requires a comma-separated responses list and validates owner access before updating. Missing query param returns a 400 error.
  • Sub-account checks – getSubAccountsResponses and getSubAccountPendingRequests ensure both the sub-account relationship and the presence of a business record; missing context yields RESOURCE_NOT_FOUND.
πŸ’¬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM