Files
marketplaces/docs/BACKEND.md
sdarbinyan 173ceb8081
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
feat(seo): tenant-driven meta tags, sitemap/robots, reduced-motion, docs
Completes the rest of Sprint 28 on top of the earlier admin-scoped
a11y/skeleton pass (576f260):

- SeoService.resetToDefaults() now reads real bootstrap.seo.default /
  branding instead of hardcoded placeholder text + a broken
  /og-image.jpg reference; auto-reapplies via an effect() whenever
  bootstrap (re)loads, same pattern as UiRuntimeFacade.
- New public/sitemap.xml (static baseline, documented per-tenant-dynamic
  limitation) + public/robots.txt Sitemap directive and admin/editor
  Disallow rules.
- Global prefers-reduced-motion override in styles.scss covering every
  existing hover-transform/fade-in/shimmer animation in one place.
- New adminProducts/adminUsers/adminMonitoring/adminAnalytics
  empty-state i18n keys (en/ru/hy) for this sprint's skeleton/empty-state
  consistency fixes.
- docs/KNOWN-ISSUES.md: logged a newly-found, much larger pre-existing
  gap (~178 missing adminXxx.* i18n keys across the whole admin
  backoffice) - deferred to Sprint 29's translation validation, not
  fixed here.
- docs/BACKEND.md: new item 17 (sitemap generation gap).
- docs/ADMIN.md, docs/SPRINT-PLAN.md: rewritten Sprint 28 sections to
  describe the full, combined scope (both commits) instead of the
  earlier admin-only framing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 00:19:41 +04:00

21 KiB

BACKEND

This is the punch list for backend engineers. Every item below is a frontend behavior that is currently mocked or local-only, with the exact gap, the endpoint(s) needed, and which frontend files change once the endpoint exists. Read this file instead of diffing the whole repo against main.

General contract rules (apply to everything below): tenant is resolved by request host, never a path/query param (docs/ARCHITECTURE.md); bootstrap/config responses must never include secrets; existing auth/payment/authorization contracts are frozen (ADR-010) — nothing here changes them.

1. Auth — admin authorization gap (highest priority, security-relevant)

Current frontend behavior: Admin login and customer login share one Telegram QR/session backend ({authApiUrl}/users/sessions) and one UI component (TelegramLoginComponent, mode: 'customer' | 'admin'). The only client-side difference is where the resulting session id is stored: webSessionID cookie (SameSite=Lax) for customers vs. adminSessionID cookie (SameSite=Strict) for admin, via separate AuthService / AdminAuthService. Admin API calls attach AdminWebSessionID via adminAuthHeadersInterceptor (core/admin-auth/admin-auth-headers.interceptor.ts).

The gap: because both flows hit the identical Telegram session endpoint, the backend has no concept of "this is an admin session" at the moment the QR is scanned — it's an ordinary Telegram user session, indistinguishable from a customer's. The frontend only decides where to store the resulting id; it cannot and does not decide whether that Telegram user is actually allowed to act as an admin. Any Telegram user who completes the QR flow while the admin login UI is showing gets an adminSessionID.

What's needed: server-side authorization check keyed off the session id (or a dedicated admin-scoped token) at the point admin API calls arrive with AdminWebSessionID — reject non-admin users. This must be enforced server-side; nothing on the frontend can substitute for it.

Frontend files that would change: src/app/core/admin-auth/admin-auth.service.ts, admin-auth-headers.interceptor.ts, admin-auth.guard.ts — only if the auth response shape changes (e.g. a role claim to check client-side in addition to the server-side enforcement).

Also prepared, not wired: core/admin-auth/ed25519-verification.model.ts defines Ed25519VerificationService (challenge/verify) for a future non-Telegram admin auth path. Current binding (NoopEd25519VerificationService, in app.config.ts) throws rather than silently accepting — safe to leave wired until a real challenge/verify endpoint exists.

2. Bootstrap draft / publish persistence (second-highest priority)

Current frontend behavior: The Project Editor (docs/EDITOR.md) edits the same BootstrapConfig the storefront consumes.

  • Load: GET /bootstrap (existing, tenant by host).
  • Save: in-memory + localStorage only (ProjectEditorDraftStorageService, key projectEditor.draftBootstrap.v1, scoped by tenant.id). Reload in another browser/tab and it's gone.
  • Publish: applies the bootstrap in-memory via PlatformRuntimeService.reloadFromBootstrap() and flips a local status flag. No backend call.

Endpoints needed (none exist yet; shapes are contracts, adjust to house conventions):

GET  /builder/bootstrap/draft
  -> tenant's current draft BootstrapConfig. 404/empty if none (draft = published).

PUT  /builder/bootstrap/draft
  body: BootstrapConfig
  -> persists the draft for this tenant. Does not affect GET /bootstrap (storefront-facing).

POST /builder/bootstrap/publish
  body: BootstrapConfig (or none, if publish always promotes the stored draft)
  -> validates, then makes this BootstrapConfig what GET /bootstrap returns. Only endpoint
     that affects the live storefront.

POST /builder/bootstrap/validate   (optional)
  body: BootstrapConfig
  -> { code: string, message: string }[], mirroring ProjectValidationIssue shape client-side.

Tenant identity: resolved by request host, same as every other endpoint — no projectId path param. One domain = one tenant = one draft = one published bootstrap.

Don't duplicate client validation, enforce it server-side too. ProjectValidator (features/project-editor/services/project-validator.service.ts) blocks Publish client-side on: missing branding.logoUrl, empty localization.supportedLocales, invalid tenant.websiteBaseUrl (must be http(s)://...), duplicate static-page identifiers (slug, falling back to route), empty homepage sections, a homepage widget with no type, duplicate header nav links, non-hex theme.palette values, missing translations for a supported locale, and unknown layout/section-layout enum values. A malicious or stale client can bypass all of this — if POST /builder/bootstrap/publish is a trust boundary, re-run equivalent checks server-side.

Frontend files that would change: features/project-editor/facade/project-editor.facade.ts (replace local save/publish with the new endpoints, keep the same public method signatures), services/project-editor-draft-storage.service.ts (becomes a fallback/offline cache rather than primary store).

3. Static pages / slug vs route inconsistency

Gap: StaticPageConfig requires slug: string, but at least one real bootstrap fixture (src/assets/mock/bootstrap/bootstrap.json) only populates route (e.g. /about-us) and leaves slug undefined. The frontend's duplicate-detection was patched to fall back to route when slug is empty, but the underlying data inconsistency remains.

Needed: decide whether slug is backend-required or auto-derived from route, so both sides agree on one source of truth going forward.

Frontend files: shared/models/config/static-page.model.ts, features/content-management/services/* (ContentPageService normalization), features/project-editor/services/project-validator.service.ts.

4. Static pages / CMS persistence

Current frontend behavior: staticPages are edited client-side in the Project Editor (create/delete page, toggle footer/header/sitemap visibility, edit slug/icon/order/translations/HTML) and only ever written into the in-memory/localStorage bootstrap draft above (item 2) — no dedicated backend for CMS content exists.

Endpoints needed:

GET  /builder/content-pages
PUT  /builder/content-pages
POST /builder/content-pages/import
GET  /builder/content-pages/export
POST /builder/content-pages/validate

Backend must also support content moderation/validation on publish (disallow dangerous tags/attributes) and revision history — the frontend only sanitizes at render time, not at authoring time (MarketplaceHtmlEditorComponent emits raw HTML with no sanitization by design; sanitization is a storefront-render concern, not an authoring concern).

Frontend files: features/content-management/facade/*, services/*, components/html-editor/*, features/project-editor/sections/footer-section.component.ts (static page list editing today lives partly here too).

5. Categories

Current behavior (Sprint 20): GET /category (existing) still backs the public storefront via CategoryDto -> CategoryMapper -> Category -> CategoryFacade, unchanged. A full admin editor now exists at features/admin/categories/ (list + create/edit, hierarchy, drag-and-drop reorder, soft delete/restore, draft/publish, SEO/translations — see docs/ADMIN.md "Sprint 20") but it runs entirely against AdminCategoriesLocalGateway, an in-memory cache seeded once from BackofficeDataService.loadCategories() (CategoryCardConfig, no hierarchy) — nothing persists across a page reload.

Gap: no admin write path (create/update/delete/reorder categories) exists on the backend. AdminCategory also carries fields the current CategoryDto/CategoryCardConfig don't have yet: parentId (hierarchy), slug, icon, imageUrl, status (draft/published), deletedAt (soft delete), seo, per-locale translations.

Needed: category CRUD endpoints matching the AdminCategory shape (src/app/features/admin/categories/models/admin-category.model.ts) plus a bulk reorder endpoint (order field) and a slug-uniqueness check (GET /admin/categories/slug-taken?slug=..., mirrors AdminCategoriesGateway.isSlugTaken).

Frontend files: implement AdminCategoriesApiGateway against AdminCategoriesGateway (services/admin-categories-gateway.interface.ts) and rebind via an injection token (same swap pattern as AdminDashboardMetricsGateway/BACKOFFICE_DATA_PROVIDER) — the facade and pages don't change. Also still open: wire admin/products' category dropdown to AdminCategoriesGateway instead of its own AdminProductsGateway.loadCategories() (Sprint 21).

6. Products

Current behavior (Sprint 21): features/admin/products/ is fully built (list + editor pages) against AdminProductsLocalGateway (swappable via an injection token, same pattern as everywhere else) — i.e. it's ready for a real API gateway, but one has never been implemented. Sprint 21 added barcode, archived (soft archive/restore), variants ({name, price, quantity}[]), relatedProductIds, and wired the category dropdown to AdminCategoriesGateway (see item 5) instead of its own seed.

Needed: product CRUD endpoints matching the current AdminProduct shape (src/app/features/admin/products/models/admin-product.model.ts) — itemID, name, price, currency, categoryID, visible, archived, discount, images, badges, media, specifications/attributes, variants, relatedProductIds, plus a bulk endpoint matching PATCH /items/bulk-style semantics for applyBulkVisibility/applyBulkDelete.

Frontend files: implement AdminProductsApiGateway alongside the existing AdminProductsLocalGateway and rebind the injection token — features/admin/products/pages/* and the facade do not change.

7. Orders / revenue

Current behavior (Sprint 23): features/admin/orders/ now exists as an admin CRUD-ish surface (list/detail, status changes, refund request, cancel, notes, CSV export, print invoice) but runs entirely against AdminOrdersLocalGateway, which fabricates 24 synthetic in-memory orders — there is still no real order data anywhere in this system. The dashboard's Orders/Revenue cards (Sprint 19) still correctly render pending-backend rather than reading from this mock (they're intentionally not wired to it — the mock is order-management scaffolding, not a real metrics source).

Needed: a real order domain — AdminOrder shape is in src/app/features/admin/orders/models/admin-order.model.ts. At minimum: order CRUD, status transitions with a timeline/audit trail, payment status, refund workflow, and a revenue aggregation endpoint for the dashboard cards.

Frontend files: implement AdminOrdersApiGateway against AdminOrdersGateway (services/admin-orders-gateway.interface.ts) and rebind via an injection token — facade and pages don't change.

8. Dashboard metrics

Current behavior: AdminDashboardMetricsGateway (token ADMIN_DASHBOARD_METRICS_GATEWAY) defaults to AdminDashboardMetricsLocalGateway, which composes BackofficeDataService.loadCategories()/loadProducts() client-side into counts. Everything else on the dashboard (marketplace status, theme, languages, last publish/save, bootstrap version, active layout, enabled widgets, system health) is derived from ProjectEditorFacade state, not a metrics endpoint.

Needed: a /builder/dashboard/summary-style endpoint returning real-time counts and trend deltas.

Frontend files: implement AdminDashboardMetricsGateway (real API version) and rebind the token in DI config — AdminDashboardFacade and all dashboard card components are unaffected (docs/ADMIN.md has the full architecture).

9. Recent Activity (admin dashboard)

Current behavior: AdminDashboardHistoryService is localStorage-backed, scoped per tenant (adminDashboard.activityHistory.v1) — same limitation pattern as the draft storage in item 2. It will never show another editor's activity.

Needed: a real audit-log endpoint (who changed what, when) that multiple admin sessions can read.

Frontend files: features/admin/dashboard/services/admin-dashboard-history.service.ts, facade/admin-dashboard.facade.ts.

10. Translations

Current behavior: all UI strings are static, compiled into the three locale files (src/app/i18n/{en,ru,hy}.ts) via the Translations interface (translations.ts) and served by TranslateService/TranslatePipe. There is no backend-editable translation surface — bootstrap-level translatable content (nav labels, static-page translations, footer copyright) is edited per-tenant through the Project Editor and stored in the bootstrap itself (see item 2), which is separate from the compiled UI-chrome strings.

Gap: UI-chrome strings (button labels, section titles, validation messages) require a frontend deploy to change or add a locale — there's no backend-driven UI-string catalog. This may be acceptable (frontend chrome vs. tenant content are different concerns) but is worth an explicit decision if a backend team expects to control all copy.

Frontend files: src/app/i18n/* (if this becomes backend-driven, it's a new i18n loading mechanism, a larger change than any other item here).

Current behavior: in-memory products/categories/tags power autocomplete and suggestions (features/search/services/search-autocomplete.service.ts); trending returns null (UI hides gracefully); search history is LocalSearchHistoryRepository (guest) with a BackendSearchHistoryRepository placeholder already defined but unimplemented.

Endpoints that would let this go live without frontend changes:

GET /search/suggestions?q={term}
GET /catalog/filters?category={id}&q={term}

Plus, if authenticated history/wishlist/compare/saved-searches sync is wanted: GET/POST/DELETE /me/wishlist, /me/compare, /me/saved-searches, /me/recently-viewed.

Frontend files: features/search/services/search-autocomplete.service.ts, search-trending.service.ts, search-history.repository.ts (swap LocalSearchHistoryRepository for BackendSearchHistoryRepository) — facade/UI unaffected by design.

12. Product engagement (rating/reviews/questions) — partially real, confirm contract

Current behavior: frontend already expects these endpoints and renders against them: GET /products/{id}/rating, GET /products/{id}/reviews?page&pageSize, GET /products/{id}/questions?page&pageSize, POST /products/{id}/reviews (rating/title/text/anonymous), POST /products/{id}/questions (text/anonymous). If these already exist, this is a contract-confirmation item, not new work; if not, they block the Reviews/Questions UI (productPage.reviews/questions config, already toggleable per tenant).

Frontend files: none, if the contract matches what's documented in docs/BOOTSTRAP.md's Product Engagement notes.


13. Transactions (Sprint 24, mock/local)

Current behavior: features/admin/transactions/ exists (list, retry-failed, fraud flag, per-transaction audit log, CSV export) against AdminTransactionsLocalGateway, which derives one synthetic transaction per seeded mock order from item 7's AdminOrdersLocalGateway — no real payment/transaction data exists.

Needed: a real payments/transactions domain (card, QR, cash-on-delivery), linked to orders, with retry semantics matching whatever the actual payment provider supports, and fraud-flag persistence.

Frontend files: implement AdminTransactionsApiGateway against AdminTransactionsGateway (services/admin-transactions-gateway.interface.ts) and rebind via an injection token.

14. Users, roles & permissions (Sprint 25, mock/local)

Current behavior: features/admin/users/ (users, built-in roles, invitations, per-user mock sessions, per-user audit log) against AdminUsersLocalGateway — fully synthetic, no backend. Passwordless login itself is real (AdminAuthService, Telegram QR, docs/BACKEND.md item 1) — only the roles/permissions/invitations/multi-session-listing layer on top is mocked.

Needed: a real user/role domain tied to the eventual server-side admin-authorization enforcement in item 1 — role assignment, a real permission catalog, invitation emails, and genuine multi-device session listing (the current AdminAuthService only ever knows about the current browser's session).

Frontend files: implement AdminUsersApiGateway against AdminUsersGateway (services/admin-users-gateway.interface.ts) and rebind via an injection token.

15. Monitoring (Sprint 26, mock/local except Health)

Current behavior: features/admin/monitoring/ — Health section reads real data (AdminDashboardFacade.healthChecks, unchanged from Sprint 19). Everything else (audit/security/login/failed-login/API/error/warning event feed, queue depths, webhook deliveries) is synthetic, seeded once in AdminMonitoringLocalGateway — no logging, queue, or webhook infrastructure exists anywhere in this system.

Needed: real structured logging with a query API (by category/level/actor/time range), real queue introspection (whatever job runner ships), and real webhook delivery tracking once webhooks exist as a feature at all.

Frontend files: implement AdminMonitoringApiGateway-equivalent methods against a to-be-defined AdminMonitoringGateway interface (AdminMonitoringLocalGateway currently has no interface extracted — add one when a real implementation is built, mirroring the pattern used everywhere else in admin/*).

16. Analytics - visitors/funnels/heatmaps (Sprint 27, no backend at all)

Current behavior: features/admin/analytics/ computes real revenue/orders/top-products aggregations from the existing mock order data (see item 7), but visitor traffic, conversion funnels, and heatmaps have zero data source anywhere in this system (no analytics/tracking pipeline, no event collection) — these render pending-backend badges rather than fabricated numbers.

Needed: a traffic/event tracking pipeline (page views, sessions, conversion events) and a funnel/heatmap aggregation service, before this section of the Analytics page can show anything real.

Frontend files: features/admin/analytics/pages/admin-analytics-page.component.html currently renders the pending-backend badge inline (no gateway method exists for this yet, unlike every other mocked domain in this doc).

17. Sitemap generation (Sprint 28, static baseline only)

Current behavior: public/sitemap.xml (new, Sprint 28) lists only the statically-known top-level marketplace routes (home/catalog/search/wishlist/compare) for the default ru locale segment, referenced from public/robots.txt's Sitemap: directive.

Gap: this is a multi-tenant, config-driven platform (docs/ARCHITECTURE.md) — supported locales, categories, products, and static pages are all resolved at runtime from each tenant's bootstrap config, not enumerable from the frontend at build time. A real per-tenant sitemap covering /:lang/product/:id, /:lang/catalog/:categoryId, and /:lang/:staticPath needs a backend/build-time job that reads the same bootstrap data source (categories/products/static pages, items 5/6/3-4 above) per tenant/domain and regenerates or serves this file dynamically — not something the SPA can produce correctly on its own.

Needed: a build-time or server-side sitemap generator with access to the real per-tenant product/category/static-page lists and domain, likely alongside whatever eventually serves bootstrap.json per-domain server-side (item 2).

Known reliability issues

Production 502/504 Bad Gateway on refresh / back-navigation

Symptom: production hits intermittent 502/504 Bad Gateway responses specifically on page refresh and browser back-navigation.

Root cause (investigation so far): environment.production.ts points the frontend at the backend API via absolute URLs directly (apiUrl: 'https://api.dexarmarket.ru:445', authApiUrl: 'https://users.vitanova.network:456'), bypassing this repo's nginx.conf entirely — that config only proxies /api for the lovero.store tenant, not dexarmarket.ru. So the 502/504 originates from that backend API's own reverse proxy (ports 445/456, a separate server not in this repo), not from anything this repo controls.

Refresh and back-navigation both re-fire session-check and bootstrap-load calls on mount (AdminAuthService.checkSession(), ConfigService.loadBootstrap(), TelegramSessionApiService), which is the likely trigger if that backend's app server or reverse proxy is crashing, overloaded, or misconfigured on those specific endpoints.

This is not fixable from this repo. It needs DevOps/backend investigation on the reverse proxy and app server fronting api.dexarmarket.ru:445 and users.vitanova.network:456 — check upstream health, timeout settings, and concurrent-connection handling around session-check and bootstrap endpoints.