CataSync feeds 4/6: OMA public feed URL, staff MCP tool, Feeds UI with field repeater
Imported from GitHub issue El-SaMa/oma#247 by @El-SaMa.
Part of the Product feeds epic. Needs "Feeds model and builder" live in the engine.
What
Staff manage feeds in OMA; comparison sites (hinta.fi, Hintaseuranta, Hintaopas/Prisjakt, ...) fetch them from a public OMA URL.
Public URL
- Next route
apps/web/app/feeds/[token]/route.ts, served ashttps://oma.lanetti.fi/feeds/<token>.xml(or.csv). It calls core-api, which calls engineGET /v1/feeds/by-token/:token. Stream the body with the engine's content type,Cache-Control: public, max-age=900. - Unknown or disabled token: plain 404 with no detail. Rate limit per IP with the limiter already used for public routes. Never redirect; never expose internal hosts. Run
scripts/public-smoke.mjsafter deploy. - Tokens: 32 random bytes, base64url. Only the sha256 is stored. Shown once on create, duplicate and rotate, with a copy button.
Staff MCP tool
oma.admin_catasync_feedswithaction:list(siteId),get(feedId),preview(feedId, limit),create(siteId, name, preset or full definition),update,duplicate,rotate_token,delete,rebuild.- Writes return a proposal + confirmToken and apply via
oma.confirm_tool(handler inservices/core-api/src/platform/confirm.ts). Engine client methods + types inservices/core-api/src/catasync/engine.ts. Zod schema inservices/mcp-server/src/tools.ts. Register in the tool registry like the otheroma.admin_catasync_*tools. - Tests in
services/core-api/src/agent-tools/tools-catasync.test.ts: validation, proposal shape, token only returned by create/duplicate/rotate confirms.
UI (apps/web/components/catasync/)
- Shops > (shop) > new side-nav item Feeds (vertical side nav, never a second tab row).
- Feed list as shadow cards: name, destination (URL / Google), format, item count, last built, skipped count, enabled toggle; buttons Edit, Duplicate, Copy URL, Rotate link, Delete (confirm modal).
- New feed modal: name, preset (Google XML, hinta.fi, Hintaseuranta, Hintaopas (Prisjakt), CSV basic, Empty), destination, format.
- Edit feed modal (wide):
- settings and filters sections
- the fields repeater: one row per field with drag handle (reorder), field name, kind (Source / Fixed text / Template), source select or value input, transform, max length, required, CDATA (XML only), remove; an "Add field" button
- a live preview panel: first 5 items + skipped counts by reason, refreshed on save
- Error codes mapped in
catasync-shared.ts; every string en/fi/sv native.
Acceptance
- Every
oma.admin_catasync_feedsaction called live through the MCP after deploy, on maxfort. - Public URL fetched from outside the LAN (a non-VPN host) returns the feed; a wrong token returns 404.
- Public smoke passes after deploy.
- Handbook: making a feed, presets, giving the URL to a comparison site, rotating a link.
Binding rules (read before coding)
- Read
CLAUDE.mdand every.cursor/rules/*.mdcwithalwaysApply: truefirst. They win over your defaults. - No OpenAI / AI calls anywhere in this feature. CataSync AI switch (
ai_enabled) stays off. Never turn it on. - Smallest correct change. Match the surrounding code style (engine = plain ESM
.mjs, mysql2 pool fromsrc/db.mjs; core-api = TypeScript). - Every staff action goes through the admin MCP (
admin-mcp-first.mdc): executor inservices/core-api/src/agent-tools/tools-catasync.ts, confirm handler inservices/core-api/src/platform/confirm.tsfor writes, registry entry, zod schema inservices/mcp-server/src/tools.ts, test intools-catasync.test.ts. The UI calls the same tool (viaapps/web/lib/catasync/admin-actions.ts), never a separate path. - UI: vertical side nav only (never a second tab row), forms in modals, shadow cards, full width (
portal-chrome.mdc). - Every string:
next-intlkey with native en, fi, sv (Finnish must read native, not translated). No em/en dashes in prose. Runnode scripts/i18n-validate.mjs. - Handbook: update
apps/web/content/admin-docs/products/catasync.md(new section) for every behaviour you add. - Secrets (tokens, Google tokens, feed tokens) never in logs, commits, handbook or chat.
- Commits: author ELSAMA only, no Co-authored-by / AI attribution. Push to
mainright after each commit (rebase onorigin/mainfirst). - Deploy: engine with
bash scripts/catasync-engine-roll.sh figaro; OMA only via the trainbash scripts/deploy-quick.sh(never setOMA_DEPLOY_SERVICES, never build on the VM). Check the live SHA contains your commit. - WordPress plugin changes: bump version, pass
scripts/wp-plugin-gate-remote.sh ops/catasync/plugins/lafu-catasync-connectorwith 0 errors 0 warnings + security check (wp-plugin-dod.mdc). Install on maxfort.fi first (test shop), only then niletech. - Never change niletech prices, statuses, shipping rows or the Code Snippets / table-rate plugins as part of this work.
- Clean up test data and temp files when done.