# golem15.faq The compiled FAQ plugin for SummerCMS applications: question and answer lists per slug and locale, edited in the admin with sanitized rich text and served to frontends as JSON. ```go import _ "git.golem15.com/golem15/sm-faq-plugin" ``` Module `git.golem15.com/golem15/sm-faq-plugin`, package `faq`, plugin ID `golem15.faq`. ## Overview The plugin is a compiled SummerCMS plugin, a port of the WinterCMS Golem15.FAQ plugin. Its `init` registers it with the framework, so an application loads it by listing it in `summer.yaml`; `summer build` then emits the blank import above into the application's `plugins.gen.go`. It requires `golem15.translate`: every FAQ belongs to one translate locale through `locale_id`, and a site keeps one row per locale under the same slug, as the PHP plugin did. `MorphName` is the PHP class string `Golem15\FAQ\Models\FAQ`. ## Features - **Table.** `golem15_faq_faqs` keeps the PHP columns: id, name, slug, locale_id (NOT NULL), is_active (default false), description (text), content (text holding the JSON list of `{question, answer}` rows) and timestamps, with a non-unique index on (slug, locale_id). One gormigrate step creates it; AutoMigrate is not used. - **Model.** `models.FAQ` stores `content` as `lagoon.Jsonable` (`models.Content`) and belongs to the translate `Locale`. Fillable: name, slug, locale_id, is_active, description, content. A name, a slug and a locale are required. - **Admin.** A top-level FAQ item (icon `circle-question-mark`, order 500) with the side item FAQs, behind the permission `golem15.faq.faqs` (developer and publisher roles). The list shows id, name, slug, locale (by name, so rows of the same slug are told apart), active and the timestamps. The form has name, slug (preset from name), locale (relation, by name), is_active, description (rich editor) and content (a repeater, "Add question", each row a required question and a rich-editor answer). Rich text is sanitized on save by the framework's policy (`seaglass`), and the save answers with a notice naming each field it cleaned, such as `content.1.answer`. - **Public API.** `GET /_faq/api/v1/faqs/{slug}` serves an active FAQ in one locale. See [Usage](#usage). - **Import.** `faq:import-winter` copies a WinterCMS site's FAQs from the JSON Lines `scripts/winter-export.sql` produces. See [CLI commands](#cli-commands). - **Phrasebooks.** `lang/en/lang.yaml` and `lang/pl/lang.yaml` keep the PHP key names. ## Usage ### Public FAQ API ``` GET /_faq/api/v1/faqs/{slug}?locale=en ``` Anonymous; the route group never attaches the backend middleware. `slug` is lowercase letters, digits and dashes. `locale` is the code of an enabled translate locale; without it the default locale is used. ```json { "data": { "name": "Shipping", "slug": "shipping", "locale": "en", "description": "
Everything about shipping.
", "content": [ { "question": "How long does shipping take?", "answer": "Two working days.
" }, { "question": "Where do you ship?", "answer": "Everywhere in the EU.
" } ] } } ``` Rules: - The row served is the active FAQ with that slug and locale; when several rows share them, the one with the lowest id. - `description` (a string or `null`) and every `answer` are HTML passed through `seaglass.Sanitize` on output, so rows that never went through the admin save (imports, SQL copies) are served clean too. `question` is a plain string. `content` is always an array. - An unknown slug, an inactive row, a malformed slug and an unknown or disabled locale are `404 {"error":"FAQ not found"}`. ### Throttling and CORS The API is throttled by the surf bucket `faq-public-api` (`faq.BucketPublicAPI`): 120 requests a minute per client IP, read behind the host's trusted proxies. The 429 body is surf's `{"message":"Too Many Attempts."}`. The plugin sends no CORS headers. A host whose frontend runs on another origin adds the path to `http.cors.paths` in `config/http.yaml`: ```yaml cors: paths: ["_faq/api/*"] allowed_origins: ["https://www.example.com"] allowed_methods: ["GET"] ``` ### Winter import 1. On the WinterCMS database, check the FAQ and translate locales table names named in the header of `scripts/winter-export.sql`, then run the read-only export: ```sh mysql -N -B --raw