Pages
| Page | Shows |
|---|---|
| Overview | Published/required counts, response and acceptance rates, star distribution, by-app and by-platform tables. |
| Surveys | Every survey with its type, enforcement, status, version and response count. Publish/archive live here. |
| Responses | The answer and consent ledger, filterable by survey, outcome and date. |
| Rating Details | Individual mobile-app ratings, filterable by app, platform, stars and date. |
| App Catalog | The apps that can be rated. |
Survey form fields
| Field | Notes |
|---|---|
| Title | Required. Shown as the prompt heading. |
| Type | Rating, Consent, Poll or Feedback. Descriptive — it does not change behaviour. |
| Enforcement | The behavioural switch. Optional or Required. |
| Message | The prompt body. Basic HTML allowed and sanitised. For consent, this is the text being agreed to. |
| App | Optionally ties the survey to a catalog app. |
| User types / Tenants | Audience filters, both multi-select. Blank = everyone. Tenants are picked by company key and appear on platform surveys only. |
| Starts / Ends | Optional active window (UTC). |
| Snooze / Cooldown / Max prompts | Pacing. Optional surveys only — greyed out when Required is selected. |
| Questions | Repeatable rows: label, type, options (one per line), required flag. |
| Publish as a new version | Edit only. Invalidates prior answers and re-asks everyone. |
Question types
| Type | Renders as |
|---|---|
| Agreement | Prose only — the Agree/Disagree buttons are the answer. |
| Star rating | Five stars, keyboard accessible. |
| Scale | Radio buttons from your option list. |
| Single choice | Radio buttons. |
| Multiple choice | Checkboxes. |
| Free text | Textarea. |
Tables (created on every database)
| Table | Holds |
|---|---|
surveys | Definitions: enforcement, version, status, targeting, pacing, content hash. |
survey_questions | Ordered questions. |
survey_responses | The append-only ledger, one row per user per survey version. |
survey_answers | Per-question answers for a response. |
survey_prompt_state | Impression counts and snooze deadlines. |
euapi_apps | The rateable app catalog (ships in the base install schema). |
euapi_app_ratings | Mobile-app ratings (ships in the base install schema). |
Routes
| Route | Purpose |
|---|---|
surveys/overview | The Overview page. |
surveys/<page> | Pages, list endpoints, modals, save/delete/publish. |
survey_prompt/submit | Where a user’s answer is posted. |
End-User API
| Endpoint | Notes |
|---|---|
GET /v1/api/me/apps | Enabled catalog apps. |
POST /v1/api/me/app-rating | Submit a 1–5 rating. One per user per app per UTC day (409 duplicate otherwise). |
GET /v1/api/me/surveys | The caller’s pending survey/consent queue (max 5, most-urgent first, questions inline). Counts one impression against the queue head — fetch to present, never poll. Gated on the mobile prompts policy switch. |
POST /v1/api/me/surveys/respond | Body {survey, action, answers}; action is answer | agree | decline | snooze | opt_out. Snooze/opt-out on a required survey → 409 action_not_allowed; a survey not pending for the caller → 404 survey_not_found; a duplicate submit is a success (status: already_recorded). |
The rating pair keeps the URLs, request bodies and response envelopes it had before the move.
All four answer 404 module_unavailable when the module — or their own policy
switch — is disabled. Full request/response contracts live in the generated OpenAPI spec
(php erpat euapi:openapi → public/v1/api/openapi.json).
Commands
php erpat migrate:modules --module=surveys # create the tables
php erpat db:seed SurveysDemo # load demo surveys
php erpat db:seed SurveysDemo --dry-run # preview only
php erpat db:seed SurveysDemo --remove # soft-delete the demo rows
php erpat module:test Surveys # run the module's tests