docs: full frontend backend-surface audit (BACKEND-AUDIT.md)
Exhaustive inventory of every HTTP call, gateway (interface + mock + real impl), facade, and model/DTO the frontend defines or expects, grouped by domain. Primary input for the remaining Backend Finalization Sprint docs. Key findings: - Only AdminCategoriesGateway and AdminDashboardMetricsGateway are DI-token-bound; every other admin domain (orders, products, users, transactions, monitoring, moderation) injects its *LocalGateway class directly - a real backend swap needs a token added first, not just a rebind. - Only one real admin API impl exists (AdminCategoriesApiGateway); everything else admin is in-memory/localStorage mock. - Content-management/project-editor have no save/publish HTTP call at all - builder writes are in-memory + localStorage draft only. - No literal /admin|/builder|/backoffice CRUD paths exist in source; concrete admin paths are proposals, not verified literals.
This commit is contained in:
823
docs/context/BACKEND-AUDIT.md
Normal file
823
docs/context/BACKEND-AUDIT.md
Normal file
@@ -0,0 +1,823 @@
|
||||
# Backend Surface Audit
|
||||
|
||||
Machine-oriented, exhaustive audit of every backend touch-point the Angular frontend
|
||||
expects — derived from the current source tree on branch `B2B`, not copied from prior
|
||||
docs. Purpose: single input for downstream backend-integration documentation tasks.
|
||||
|
||||
Legend for maturity (mirrors `docs/BACKEND_API.md`'s tagging so the two stay reconcilable):
|
||||
|
||||
- **LIVE** — real `HttpClient` call exists in code today (file cited).
|
||||
- **MOCK-SWAPPABLE** — interface + mock implementation exist, wired through an Angular
|
||||
DI token so a real `*Api*` class can be dropped in without touching UI. A real impl
|
||||
may or may not exist yet.
|
||||
- **MOCK-ONLY (no seam)** — mock/local implementation exists but the facade injects the
|
||||
concrete local class **directly** (no DI token). Adding a backend here first requires
|
||||
introducing a token seam. This is the single most important structural finding below.
|
||||
- **LOCAL-ONLY** — never talks to a backend by design (localStorage / in-memory /
|
||||
derived from already-loaded bootstrap). Listed for completeness.
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Executive summary & key findings](#1-executive-summary--key-findings)
|
||||
2. [Runtime provider strategy & environment](#2-runtime-provider-strategy--environment)
|
||||
3. [HTTP interceptor pipeline](#3-http-interceptor-pipeline)
|
||||
4. [Live HTTP endpoints (verified in code)](#4-live-http-endpoints-verified-in-code)
|
||||
5. [Domain: Auth (customer + admin)](#5-domain-auth-customer--admin)
|
||||
6. [Domain: Bootstrap / config / tenant](#6-domain-bootstrap--config--tenant)
|
||||
7. [Domain: Products & catalog](#7-domain-products--catalog)
|
||||
8. [Domain: Categories](#8-domain-categories)
|
||||
9. [Domain: Backoffice storefront data](#9-domain-backoffice-storefront-data)
|
||||
10. [Domain: Cart / orders / payments](#10-domain-cart--orders--payments)
|
||||
11. [Domain: Reviews & questions (engagement)](#11-domain-reviews--questions-engagement)
|
||||
12. [Domain: Location / regions](#12-domain-location--regions)
|
||||
13. [Domain: Widgets / dynamic renderer](#13-domain-widgets--dynamic-renderer)
|
||||
14. [Admin gateways (feature area)](#14-admin-gateways-feature-area)
|
||||
15. [Domain: Media library](#15-domain-media-library)
|
||||
16. [Domain: Content management / static pages](#16-domain-content-management--static-pages)
|
||||
17. [Domain: Project editor / builder](#17-domain-project-editor--builder)
|
||||
18. [Domain: Search](#18-domain-search)
|
||||
19. [Domain: User experience (wishlist/compare/etc.)](#19-domain-user-experience-wishlistcomparetc)
|
||||
20. [Domain: Diagnostics](#20-domain-diagnostics)
|
||||
21. [Facade catalog](#21-facade-catalog)
|
||||
22. [Gateway / provider master table](#22-gateway--provider-master-table)
|
||||
23. [Model / DTO catalog](#23-model--dto-catalog)
|
||||
24. [Endpoint URL literals found in code](#24-endpoint-url-literals-found-in-code)
|
||||
25. [Cross-check against existing docs](#25-cross-check-against-existing-docs)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive summary & key findings
|
||||
|
||||
- **~9 real HTTP-speaking domains** exist today: product/catalog, categories, cart/orders/
|
||||
payments, reviews/questions, telegram session auth, bootstrap, backoffice storefront data,
|
||||
widget manifest, location/regions. Plus **Ed25519 admin auth** — wired to real `HttpClient`
|
||||
but the endpoints are not implemented server-side yet (calls 404 today, by design).
|
||||
- **Two provider seams are token-bound and API-ready today**: `PRODUCT_DATA_PROVIDER`
|
||||
(→ `ApiProductDataProvider`, LIVE) and `CATEGORY_REPOSITORY` (→ `ApiCategoryRepository`, LIVE),
|
||||
plus `CONFIG_PROVIDER` and `BACKOFFICE_DATA_PROVIDER` which switch mock↔api by strategy.
|
||||
- **KEY STRUCTURAL FINDING — most admin CRUD domains have no swap seam.** Of the 11 admin
|
||||
gateway domains, only **categories** (`ADMIN_CATEGORIES_GATEWAY`) and **dashboard-metrics**
|
||||
(`ADMIN_DASHBOARD_METRICS_GATEWAY`) are injected via DI token. The other 9 (orders, products,
|
||||
users, transactions, monitoring, moderation, customers, analytics, and the products/orders
|
||||
gateways reused by analytics/customers) have their facades inject the concrete
|
||||
`Admin*LocalGateway` **class directly**. A backend engineer cannot "just rebind a token" for
|
||||
those — a token must be introduced first. This partially contradicts the blanket
|
||||
"PLANNED / rebind the token" framing in `docs/BACKEND_API.md`.
|
||||
- **Only one real `*Api*Gateway` exists in the admin area**: `AdminCategoriesApiGateway`
|
||||
(`src/app/features/admin/categories/services/admin-categories-api.gateway.ts`). Every other
|
||||
admin domain is local-mock only.
|
||||
- **Media** is bound by class token (`MediaRepository` abstract class → `MockMediaRepository`
|
||||
via `app.config.ts`), so it is MOCK-SWAPPABLE but no real impl exists.
|
||||
- **Content-management and project-editor never hit a dedicated backend** — they read/mutate
|
||||
the in-memory `BootstrapConfig` (loaded once from `GET /bootstrap`) and persist drafts to
|
||||
localStorage. Publishing a marketplace = writing bootstrap back, for which no client write
|
||||
call exists yet (LOCAL-ONLY today; a builder publish endpoint is FUTURE).
|
||||
- **Two API base URLs are in play**: the tenant marketplace API (`ApiConfigService.getBaseUrl()`,
|
||||
default `https://api.dexarmarket.ru:445`, `/api` on localhost) and a separate payment/QR API
|
||||
(`environment.qrApiUrl` = `https://qr.vitanova.network/api`). Auth session API uses
|
||||
`environment.authApiUrl` (= `https://api.dexarmarket.ru:445`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Runtime provider strategy & environment
|
||||
|
||||
`src/app/core/providers/runtime-provider-strategy.service.ts` — `RuntimeProviderStrategyService`
|
||||
decides mock vs api per domain. Modes: `'mock' | 'api' | 'remote-config'`.
|
||||
|
||||
| Method | Returns `mock` when | Else |
|
||||
|---|---|---|
|
||||
| `getBootstrapProviderMode()` | `useMockData` true, OR `useMockBootstrapOnLocal && isLocalhost()` | `api` |
|
||||
| `getBackofficeProviderMode()` | `useMockData` true | `api` |
|
||||
| `getProductProviderMode()` | `useMockData` true | `api` (mock/remote-config fall through to api in token factory) |
|
||||
| `getCategoryProviderMode()` | `useMockData` true, OR `useMockBootstrapOnLocal && isLocalhost()` | `api` |
|
||||
|
||||
Note: `PRODUCT_DATA_PROVIDER` and `CATEGORY_REPOSITORY` token factories currently return the
|
||||
**Api** provider for every mode (the `case 'mock'` falls through) — there is no mock product/
|
||||
category provider class bound. `getCategoryProviderMode()` returning `mock` only matters for
|
||||
`ADMIN_CATEGORIES_GATEWAY` (which does honor it → `AdminCategoriesLocalGateway`).
|
||||
|
||||
`src/environments/environment.ts` relevant keys:
|
||||
|
||||
```
|
||||
useMockData: false
|
||||
useMockBootstrapOnLocal: true
|
||||
allowBootstrapApiOverride: false
|
||||
localhostApiUrl: '/api'
|
||||
tenantApiTemplate: 'https://{tenant}.api.dexarmarket.ru:445'
|
||||
tenantApiBaseUrls: { default: 'https://api.dexarmarket.ru:445', dexarmarket: 'https://api.dexarmarket.ru:445' }
|
||||
apiUrl: '/api'
|
||||
authApiUrl: 'https://api.dexarmarket.ru:445'
|
||||
qrApiUrl: 'https://qr.vitanova.network/api'
|
||||
telegramBot: 'myAMLKYCBOT' (fallback in code: 'DexarSupport_bot')
|
||||
```
|
||||
|
||||
`src/app/core/config/api-config.service.ts` — `ApiConfigService.getBaseUrl()` resolves the
|
||||
tenant marketplace API base: localhost → `localhostApiUrl`; else `tenantApiBaseUrls[tenantKey]`;
|
||||
else `tenantApiTemplate` with `{tenant}` substituted; else optional bootstrap override
|
||||
(gated by `allowBootstrapApiOverride`, reads `bootstrap.apiEndpoints.website.baseUrl` /
|
||||
`bootstrap.tenant.apiBaseUrl`). `isApiRequest(url)` = starts with `/api` or the base URL.
|
||||
`toApiUrl(url)` rewrites a `/api`-prefixed relative URL onto the resolved base.
|
||||
|
||||
Tenant key comes from `TenantResolverService` (`src/app/core/config/tenant-resolver.service.ts`).
|
||||
|
||||
---
|
||||
|
||||
## 3. HTTP interceptor pipeline
|
||||
|
||||
Registered in `src/app/app.config.ts` in this order:
|
||||
|
||||
```
|
||||
withInterceptors([mockDataInterceptor, apiBaseUrlInterceptor, apiHeadersInterceptor, adminAuthHeadersInterceptor, cacheInterceptor])
|
||||
```
|
||||
|
||||
| Interceptor | File | Responsibility |
|
||||
|---|---|---|
|
||||
| `mockDataInterceptor` | `src/app/interceptors/mock-data.interceptor.ts` | When `environment.useMockData`, short-circuits marketplace endpoints with in-memory fixtures (categories, items, search, cart, qr, callback, purchase-email, sessions). Matches URL patterns — see §24. |
|
||||
| `apiBaseUrlInterceptor` | `src/app/interceptors/api-base-url.interceptor.ts` | Rewrites `/api/*` relative URLs to `ApiConfigService.toApiUrl()`. |
|
||||
| `apiHeadersInterceptor` | `src/app/interceptors/api-headers.interceptor.ts` | For marketplace API requests, sets headers: `X-Region`, `X-Language` (RU/EN/AM), `Currency` (default RUB), `WebSessionID` (auth session id or persisted anonymous 32-hex id in localStorage key `web_session_id`). |
|
||||
| `adminAuthHeadersInterceptor` | `src/app/core/admin-auth/admin-auth-headers.interceptor.ts` | For requests whose URL contains `/admin/`, `/backoffice/`, `/builder/`, `/media/`, sets `AdminWebSessionID` header (from `AdminAuthService.session()`) and `Authorization: Bearer <token>` if an admin token is stored. |
|
||||
| `cacheInterceptor` | `src/app/interceptors/cache.interceptor.ts` | Client-side GET response caching. |
|
||||
|
||||
Header value maps (from `apiHeadersInterceptor`): language `ru→RU, en→EN, hy→AM`;
|
||||
region `moscow→Moscow, spb→ST. Petersburg, yerevan→Yerevan`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Live HTTP endpoints (verified in code)
|
||||
|
||||
All paths relative to `ApiConfigService.getBaseUrl()` unless a full origin is shown. Payment
|
||||
endpoints use `environment.qrApiUrl`; session endpoints use `environment.authApiUrl`.
|
||||
|
||||
### Marketplace API — `src/app/services/api.service.ts` (`ApiService`)
|
||||
|
||||
| Method | HTTP | Path | Notes |
|
||||
|---|---|---|---|
|
||||
| `ping()` | GET | `/ping` | `{ message }` |
|
||||
| `getCategories()` | GET | `/category` | normalized to `Category[]` |
|
||||
| `getCategoryItems(id,count,skip)` | GET | `/category/{categoryID}?count&skip` | `Item[]` |
|
||||
| `getItem(id)` | GET | `/items/{itemID}` | single `Item` |
|
||||
| `searchItems(search,count,skip,opts)` | GET | `/searchitems?search&count&skip[&categoryIDs&minPrice&maxPrice&tag&sort]` | `{ items, total }` |
|
||||
| `getRandomItems(count,categoryID?)` | GET | `/items/randomitems?count[&category]` | `Item[]` (featured) |
|
||||
| `addToCart(sessionId,items)` | POST | `/websession/{sessionId}` | body = item array |
|
||||
| `submitReview(data)` | POST | `/items/{itemID}/callback` | body: rating, comment, sessionID, timestamp |
|
||||
| `submitQuestion(data)` | POST | `/items/{itemID}/questiion` | **NOTE: literal typo `questiion`** matches backend spec |
|
||||
| `createCartPayment(payload)` | POST | `/cart` | `CartPaymentRequest` → `QrCreateResponse` |
|
||||
| `createOrder(payload)` | POST | `/orders` | `CreateOrderRequest` → `CreateOrderResponse`; fire-and-forget after payment |
|
||||
| `submitPurchaseEmail(data)` | POST | `/purchase-email` | email receipt |
|
||||
| `createPayment(payload,headers)` | POST | `{qrApiUrl}/qr` | headers `authorization-key`, `userid-value` |
|
||||
| `checkCartPaymentStatus(qrId)` | GET | `{qrApiUrl}/qr/dynamic/{partnerId}/{qrId}` | partnerId const `web-97ec-9c57-4dde-9037-3a68f7f83750` |
|
||||
| `checkCartCardPaymentStatus(orderId)` | GET | `{qrApiUrl}/card/{partnerId}/{orderId}` | |
|
||||
| `checkPaymentStatus(partnerQrId,qrId)` | GET | `{qrApiUrl}/qr/dynamic/{partnerQrId}/{qrId}` | |
|
||||
|
||||
Also builds an external QR image URL (`https://api.qrserver.com/v1/create-qr-code/...`) — not a backend of this platform.
|
||||
|
||||
### Other live callers
|
||||
|
||||
| Caller (file) | HTTP | Path | Base |
|
||||
|---|---|---|---|
|
||||
| `ApiHealthService` (`src/app/services/api-health.service.ts`) | GET | `/ping` | marketplace base |
|
||||
| `ApiCategoryRepository` (`src/app/core/categories/repositories/api-category.repository.ts`) | GET | `/category` | marketplace base; retry x2 |
|
||||
| `ApiBootstrapProvider` (`src/app/core/bootstrap/providers/api-bootstrap.provider.ts`) | GET | `/bootstrap` | relative |
|
||||
| `MockBootstrapProvider` | GET | `/assets/mock/bootstrap/bootstrap.json` | static asset |
|
||||
| `ApiBackofficeDataProvider` (`src/app/core/backoffice/providers/api-backoffice-data.provider.ts`) | GET | `/api/backoffice/products`, `/api/backoffice/categories` | |
|
||||
| `WidgetManifestService` (`src/app/widgets/registry/widget-manifest.service.ts`) | GET | `bootstrap.widgetRegistry.manifestUrl` or `/assets/mock/bootstrap/widget-manifest.json` | |
|
||||
| `LocationService` (`src/app/services/location.service.ts`) | GET | `/regions` (marketplace base); `http://ip-api.com/json/...` (external geo-IP) | |
|
||||
| `TelegramSessionApiService` (`src/app/services/telegram-session-api.service.ts`) | POST/GET/DELETE | `{authApiUrl}/users/sessions`, `/users/sessions/{id}` | session auth |
|
||||
| `AuthApiService` (`src/app/core/auth/services/auth-api.service.ts`) | GET/POST | `{authApiUrl}/api/admin/auth/challenge|verify|refresh|logout` | **not implemented server-side yet** |
|
||||
| `ApiProductDataProvider` | (delegates to `ApiService`) | see above | |
|
||||
|
||||
---
|
||||
|
||||
## 5. Domain: Auth (customer + admin)
|
||||
|
||||
Two distinct auth mechanisms coexist.
|
||||
|
||||
### 5a. Telegram session auth (LIVE) — customer AND admin
|
||||
|
||||
`src/app/services/telegram-session-api.service.ts` — `TelegramSessionApiService`. Single source
|
||||
for both customer (`AuthService`) and admin (`AdminAuthService`) login; there is no separate
|
||||
admin backend endpoint. Only storage is kept separate (distinct cookie/signals).
|
||||
|
||||
| Method | HTTP | Path | Request | Response (normalized) |
|
||||
|---|---|---|---|---|
|
||||
| `createSession()` | POST | `{authApiUrl}/users/sessions` | `{ webSessionID }` + header `WebSessionID` | `WebSessionStart { webSessionID, url }` (url = `https://t.me/{bot}?start={id}`) |
|
||||
| `checkSessionOnce(id)` | GET | `{authApiUrl}/users/sessions/{id}` | — | `AuthSession | null` (heavily field-tolerant normalizer) |
|
||||
| `logout(id)` | DELETE | `{authApiUrl}/users/sessions/{id}` | header `WebSessionID` | ignored |
|
||||
|
||||
Consumers: `AuthService` (`src/app/services/auth.service.ts`, customer),
|
||||
`AdminAuthService` (`src/app/core/admin-auth/admin-auth.service.ts`, admin — separate cookie
|
||||
`adminSessionID`, has dev-only `devBypassLogin()`), `AuthFacade`
|
||||
(`src/app/core/auth/services/auth-facade.service.ts`) wrapping AuthService/SessionService/
|
||||
PermissionService for components. Also `src/app/shared/qr-login/`.
|
||||
|
||||
Models: `AuthSession`, `WebSessionStart`, `AuthStatus` (`src/app/models/auth.model.ts`);
|
||||
`AdminAuthStatus` (`src/app/models/admin-auth.model.ts`).
|
||||
|
||||
### 5b. Ed25519 challenge/response admin auth (LIVE wiring, backend absent)
|
||||
|
||||
`src/app/core/auth/services/auth-api.service.ts` — `AuthApiService`. Real `HttpClient` wiring
|
||||
against a documented contract that the backend has NOT implemented yet (calls 404 today,
|
||||
mapped to a `backend-unavailable` error screen). No mocks fabricated.
|
||||
|
||||
| Method | HTTP | Path (`{authApiUrl}/api/admin/auth`) | Request | Response |
|
||||
|---|---|---|---|---|
|
||||
| `requestChallenge()` | GET | `/challenge` | — | `AuthChallenge { nonce, issuedAt, expiresAt }` |
|
||||
| `verifySignature(req)` | POST | `/verify` | `VerifySignatureRequest { publicKey, signature, nonce }` | `AuthTokenPair { token, refreshToken }` |
|
||||
| `refresh(req)` | POST | `/refresh` | `RefreshTokenRequest { refreshToken }` | `AuthTokenPair` |
|
||||
| `logout(refreshToken)` | POST | `/logout` | `{ refreshToken }` | void |
|
||||
|
||||
Models: `src/app/core/auth/models/auth-api.model.ts` (`AuthChallenge`, `VerifySignatureRequest`,
|
||||
`AuthTokenPair`, `RefreshTokenRequest`, `JwtClaims`). Supporting:
|
||||
`src/app/core/auth/services/ed25519-keypair.service.ts` (keypair gen/signing);
|
||||
`src/app/core/admin-auth/ed25519-verification.model.ts` +
|
||||
`noop-ed25519-verification.service.ts` (bound in `app.config.ts` via
|
||||
`{ provide: Ed25519VerificationService, useClass: NoopEd25519VerificationService }`).
|
||||
|
||||
Permissions/roles: `src/app/core/auth/models/permission.model.ts` — `AdminRole`
|
||||
(`Owner|Administrator|Editor|Support|ReadOnly`), `Permission` union.
|
||||
Errors: `src/app/core/auth/models/auth-error.model.ts` — `AuthErrorCode`, `AuthError`.
|
||||
Guards: `src/app/core/admin-auth/admin-auth.guard.ts`, `src/app/guards/**`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Domain: Bootstrap / config / tenant
|
||||
|
||||
The runtime configuration document that drives the entire multi-tenant platform.
|
||||
|
||||
- **Contract interface**: `ConfigProvider` (`src/app/core/config/config-provider.interface.ts`)
|
||||
— `loadBootstrap(): Observable<BootstrapConfig>`.
|
||||
- **DI token**: `CONFIG_PROVIDER` (`src/app/core/config/config-provider.token.ts`), factory
|
||||
switches on `getBootstrapProviderMode()`: `mock` → `MockBootstrapProvider`
|
||||
(`/assets/mock/bootstrap/bootstrap.json`), else → `ApiBootstrapProvider` (`GET /bootstrap`).
|
||||
- **Implementations**: `ApiBootstrapProvider`, `MockBootstrapProvider`
|
||||
(`src/app/core/bootstrap/providers/*`).
|
||||
- **Consuming services**: `ConfigService` (`src/app/core/config/config.service.ts`,
|
||||
holds the bootstrap snapshot), `ApiConfigService`, `FeatureConfigService`,
|
||||
`TenantResolverService`, `FooterResolverService`, `StaticPageResolverService`
|
||||
(all `src/app/core/config/*`).
|
||||
- **Consuming facades**: `UiRuntimeFacade` (`src/app/facades/runtime/ui-runtime.facade.ts`),
|
||||
`WebsiteRuntimeFacade` (`src/app/facades/website/website-runtime.facade.ts`),
|
||||
`ProjectEditorFacade`, `ContentManagementFacade`, `DiagnosticsFacade`.
|
||||
|
||||
`BootstrapConfig` (`src/app/shared/models/config/bootstrap-config.model.ts`) aggregates ~24
|
||||
sub-configs, each its own file under `src/app/shared/models/config/`:
|
||||
|
||||
`schemaVersion, generatedAt, tenant, branding, theme, company, featureFlags, features?,
|
||||
apiEndpoints, localization, seo, permissions, header?, catalog?, layout?, navigation, footer?,
|
||||
productPage?, userExperience?, pages[], staticPages?, widgetRegistry?`
|
||||
|
||||
Sub-config model files (all backend-shaped, served inside bootstrap):
|
||||
`api-endpoints.model.ts` (`ApiEndpointConfig{path,method,timeoutMs?}`, `ApiEndpointsConfig{
|
||||
bootstrap, website:Record<...>, builder:Record<...>, backoffice:Record<...>}`),
|
||||
`tenant.model.ts` (`TenantConfig{id,slug,code,host,name,websiteBaseUrl,builderBaseUrl,
|
||||
backofficeBaseUrl,defaultLocale,supportedLocales,defaultCurrency,supportedCurrencies,timezone}`),
|
||||
`branding.model.ts`, `theme.model.ts`, `company.model.ts`, `feature-flags.model.ts`,
|
||||
`features-config.model.ts`, `footer-config.model.ts`, `header-config.model.ts`, `layout.model.ts`,
|
||||
`localization.model.ts`, `navigation.model.ts`, `page.model.ts`, `permissions.model.ts`,
|
||||
`product-page-config.model.ts`, `catalog-config.model.ts`, `seo.model.ts`,
|
||||
`static-page.model.ts`, `user-experience-config.model.ts`, `widget-registry.model.ts`,
|
||||
`widget.model.ts`, `section.model.ts`. Barrel: `src/app/shared/models/config/index.ts`.
|
||||
|
||||
`ApiEndpointsConfig.website/builder/backoffice` are `Record<string, ApiEndpointConfig>` —
|
||||
i.e. the bootstrap document is where a tenant's PLANNED endpoint paths are declared at runtime.
|
||||
No literal builder/backoffice path constants exist in code (see §24).
|
||||
|
||||
---
|
||||
|
||||
## 7. Domain: Products & catalog
|
||||
|
||||
- **Contract interface**: `ProductDataProvider`
|
||||
(`src/app/core/products/providers/product-data-provider.interface.ts`).
|
||||
- **DI token**: `PRODUCT_DATA_PROVIDER` (`src/app/core/products/product-data-provider.token.ts`)
|
||||
— factory returns `ApiProductDataProvider` for all modes (no mock provider class bound).
|
||||
- **Real impl (LIVE)**: `ApiProductDataProvider`
|
||||
(`src/app/core/products/providers/api-product-data.provider.ts`) — delegates to `ApiService`
|
||||
+ `CategoryService`; contains inline mapping (item→reviews/questions/rating summary).
|
||||
- **Domain service**: `ProductDataService` (`src/app/core/products/product-data.service.ts`)
|
||||
injected by `ProductFacade`.
|
||||
- **Consuming facade**: `ProductFacade` (`src/app/facades/platform/product.facade.ts`).
|
||||
- **Consuming components**: `catalog-container.component.ts`,
|
||||
`product-details-container.component.ts` (`src/app/features/website/**`), home/catalog pages.
|
||||
|
||||
Interface methods: `getProducts(query?)`, `getProduct(productID)`, `getCategories()`,
|
||||
`searchProducts(query)`, `getFeaturedProducts(query?)`, `getLatestProducts(query?)`,
|
||||
`getProductsByCategory(categoryID,query?)`, `getRelatedProducts(query)`, `loadRating(productID)`,
|
||||
`loadReviews(productID,query?)`, `loadQuestions(productID,query?)`, `submitReview(productID,input)`,
|
||||
`submitQuestion(productID,input)`.
|
||||
|
||||
Models — `src/app/core/products/models/`:
|
||||
- `product-domain.model.ts`: `Product = Item` (alias), `ProductCategory = Category`,
|
||||
`ProductSort`, `ProductFilters`, `ProductListQuery`, `ProductSearchQuery`, `ProductListResult`,
|
||||
`RelatedProductsQuery`, `RelatedProductCollection`, `ProductVariantSelection`.
|
||||
- `product-engagement.model.ts`: `RatingStars`, `RatingDistributionEntry`, `RatingSummary`,
|
||||
`Review`, `Answer`, `Question`, `EngagementListQuery`, `EngagementListResult<T>`,
|
||||
`SubmitReviewInput`, `SubmitQuestionInput`.
|
||||
- `catalog-experience.model.ts`: `SearchCriteria`, `FilterDefinition`, `FilterOption`,
|
||||
`SortDefinition`, `CatalogView`, `SearchResult`, layout/nav mode types.
|
||||
|
||||
The **backend-shaped** product DTO is `Item` (`src/app/models/item.model.ts`) — the raw wire
|
||||
shape. `ApiService.normalizeItem()` is the adapter: it reconciles legacy marketplace format and
|
||||
newer backOffice format (string `id`↔numeric `itemID`, `imgs[]`↔`photos[]`, `names[]`↔
|
||||
`translations`, `itemDetails[]`, `description` key/value array↔string, `comments`↔`callbacks`,
|
||||
`specificationGroups`, `variantOptions`, `relatedCollections`, delivery normalization, color
|
||||
`0xRRGGBB`→`#RRGGBB`, remaining→stock band). This is the single largest inline mapper in the
|
||||
codebase — a backend engineer should treat `normalizeItem`/`normalizeCategory` as the tolerance
|
||||
contract. `Item` supporting types: `ProductMedia`, `DescriptionField`, `ItemName`,
|
||||
`ProductSpecificationField/Group`, `ProductVariantOption(Group)`, `RelatedProductCollection`,
|
||||
`DeliveryOption`, `ItemDetail`, `CartItem`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Domain: Categories
|
||||
|
||||
Two parallel category stacks exist (legacy + clean-architecture):
|
||||
|
||||
**Clean stack (MOCK-SWAPPABLE, real impl LIVE):**
|
||||
- **Interface**: `CategoryRepository` (`src/app/core/categories/repositories/category.repository.ts`)
|
||||
— `getCategories(): Observable<CategoryDto[]>`.
|
||||
- **DI token**: `CATEGORY_REPOSITORY` (`src/app/core/categories/category-repository.token.ts`)
|
||||
→ `ApiCategoryRepository` for all modes.
|
||||
- **Real impl (LIVE)**: `ApiCategoryRepository` — `GET /category`, retry x2.
|
||||
- **DTO**: `CategoryDto`, `CategoryNameDto` (`src/app/core/categories/dto/category.dto.ts`).
|
||||
- **Adapter**: `CategoryMapper` (`src/app/core/categories/mappers/category.mapper.ts`) —
|
||||
`CategoryDto → Category` domain (flattens subcategory tree, dedupes by id, language
|
||||
normalization `am→hy`).
|
||||
- **Domain model**: `Category`, `CategoryTranslation`
|
||||
(`src/app/core/categories/models/category-domain.model.ts`).
|
||||
- **Facade**: `CategoryFacade` (`src/app/facades/platform/category.facade.ts`) via
|
||||
`CategoryService` (`src/app/core/categories/category.service.ts`). Utils:
|
||||
`category-tree.utils.ts`.
|
||||
|
||||
**Legacy stack**: `ApiService.getCategories()` → `Category` (`src/app/models/category.model.ts`,
|
||||
with `Subcategory`) via `normalizeCategory()`. Used by `ApiProductDataProvider.getCategories()`.
|
||||
Note: two different `Category` types exist (`src/app/models/category.model.ts` vs
|
||||
`src/app/core/categories/models/category-domain.model.ts`) — a known duplication.
|
||||
|
||||
---
|
||||
|
||||
## 9. Domain: Backoffice storefront data
|
||||
|
||||
Storefront-facing "cards" data (distinct from the admin/backoffice feature area).
|
||||
|
||||
- **Interface**: `BackofficeDataProvider`
|
||||
(`src/app/core/backoffice/providers/backoffice-data-provider.interface.ts`) —
|
||||
`loadProducts(): Observable<ProductCardConfig[]>`, `loadCategories(): Observable<CategoryCardConfig[]>`.
|
||||
- **DI token**: `BACKOFFICE_DATA_PROVIDER` (`src/app/core/backoffice/backoffice-data-provider.token.ts`)
|
||||
— `mock` → `MockBackofficeDataProvider`, else → `ApiBackofficeDataProvider`.
|
||||
- **Impls**: `ApiBackofficeDataProvider` (LIVE, `GET /api/backoffice/products`,
|
||||
`GET /api/backoffice/categories`), `MockBackofficeDataProvider`
|
||||
(`src/app/core/backoffice/providers/*`).
|
||||
- **Models**: `ProductCardConfig` (`src/app/shared/models/ui/product-card.model.ts`),
|
||||
`CategoryCardConfig` (`src/app/shared/models/ui/category-card.model.ts`),
|
||||
`ButtonConfig` (`button.model.ts`). Barrel: `src/app/shared/models/ui/index.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Domain: Cart / orders / payments
|
||||
|
||||
Cart state is **LOCAL-ONLY** but checkout produces LIVE payment/order calls.
|
||||
|
||||
- **`CartService`** (`src/app/services/cart.service.ts`) — signal-based cart, persisted to
|
||||
localStorage key `marketplace_cart` (+ Telegram CloudStorage when in Telegram WebApp). No
|
||||
backend for cart contents. Models: `CartItem` (extends `Item`), `DeliveryOption`.
|
||||
- **Checkout → `ApiService`** (see §4): `POST /cart` (`CartPaymentRequest`),
|
||||
`POST /orders` (`CreateOrderRequest`→`CreateOrderResponse`), `POST /purchase-email`,
|
||||
QR/card status polling on `qrApiUrl`.
|
||||
- Request/response DTOs live inline in `api.service.ts`: `QrCreateRequest`, `QrCreateResponse`,
|
||||
`CartPaymentRequest`, `CreateOrderRequest`, `CreateOrderResponse`, `QrDynamicStatusResponse`.
|
||||
- Admin-side order/transaction views are a **separate** mock domain — see §14.
|
||||
|
||||
---
|
||||
|
||||
## 11. Domain: Reviews & questions (engagement)
|
||||
|
||||
Customer-facing. LIVE via `ApiService`. Interface methods on `ProductDataProvider`:
|
||||
`loadRating`, `loadReviews`, `loadQuestions`, `submitReview`, `submitQuestion`.
|
||||
Endpoints: `POST /items/{id}/callback` (review), `POST /items/{id}/questiion` (question, typo
|
||||
preserved). Reads derive reviews/questions/rating from `GET /items/{id}` payload (no dedicated
|
||||
list endpoints yet). Models in `product-engagement.model.ts` (§7). Admin **moderation** of
|
||||
reviews/reports is a separate mock domain — see §14.
|
||||
|
||||
---
|
||||
|
||||
## 12. Domain: Location / regions
|
||||
|
||||
`LocationService` (`src/app/services/location.service.ts`), LIVE:
|
||||
- `GET /regions` (marketplace base) → `Region[]`; falls back to 6 hardcoded regions on error.
|
||||
- `GET http://ip-api.com/json/?fields=...` (external geo-IP, no key) for auto-detect.
|
||||
Models: `Region`, `GeoIpResponse` (`src/app/models/location.model.ts`). Region id feeds the
|
||||
`X-Region` header (§3).
|
||||
|
||||
---
|
||||
|
||||
## 13. Domain: Widgets / dynamic renderer
|
||||
|
||||
Widget manifest is LIVE (static/remote JSON), widget data is derived from products/categories.
|
||||
|
||||
- **`WidgetManifestService`** (`src/app/widgets/registry/widget-manifest.service.ts`) — GETs
|
||||
`bootstrap.widgetRegistry.manifestUrl` or fallback
|
||||
`/assets/mock/bootstrap/widget-manifest.json` → `WidgetManifestFile`.
|
||||
- **`WidgetRegistryService`** (`src/app/widgets/registry/widget-registry.service.ts`),
|
||||
**`WidgetHostService`** (`src/app/dynamic-renderer/widget-host/widget-host.service.ts`).
|
||||
- Contracts (`src/app/widgets/contracts/`): `widget-manifest.contract.ts`
|
||||
(`WidgetManifestEntry/File`, `WidgetSettingsSchema`, `WidgetMetadataSupport`,
|
||||
`WidgetLayoutSupport`, `WidgetDataSourceName`), `widget-component.contract.ts`
|
||||
(`WidgetRenderContext`, `RegisteredWidget`, `ResolvedWidget`), `widget-data.contract.ts`
|
||||
(`HeroWidgetData`, `CategoriesWidgetData`, `ProductCollectionWidgetData`, `BannerWidgetData`,
|
||||
`HtmlWidgetData`, `PartnersWidgetData`, `FooterWidgetData`, `HeroSlideData`,
|
||||
`WidgetResolvedContext`).
|
||||
- Renderer models: `src/app/dynamic-renderer/{page-renderer,section-renderer,widget-host}/*.model.ts`.
|
||||
- Widget data sources (`featured|latest|category|manual|related|root|parent`) map back onto
|
||||
the product/category providers of §7–§8.
|
||||
|
||||
---
|
||||
|
||||
## 14. Admin gateways (feature area)
|
||||
|
||||
`src/app/features/admin/**`. Each domain follows Facade → Gateway (interface) → LocalGateway.
|
||||
**Only categories and dashboard-metrics use a DI token; all others inject the local class
|
||||
directly (MOCK-ONLY, no seam).** Only `AdminCategoriesApiGateway` is a real HTTP impl.
|
||||
|
||||
| Domain | Interface | Local (mock) impl | Real impl | DI token | Facade | Seam status |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Categories | `admin-categories-gateway.interface.ts` (`AdminCategoriesGateway`) | `admin-categories-local.gateway.ts` | `admin-categories-api.gateway.ts` (**HttpClient**) | `ADMIN_CATEGORIES_GATEWAY` (`admin-categories-gateway.token.ts`) | `AdminCategoriesFacade` | MOCK-SWAPPABLE (real impl exists) |
|
||||
| Dashboard metrics | `admin-dashboard-metrics.gateway.interface.ts` (`AdminDashboardMetricsGateway`) | `admin-dashboard-metrics.local.gateway.ts` | none | `ADMIN_DASHBOARD_METRICS_GATEWAY` (`admin-dashboard-metrics-gateway.token.ts`) | `AdminDashboardFacade` | MOCK-SWAPPABLE (token only) |
|
||||
| Orders | `admin-orders-gateway.interface.ts` (`AdminOrdersGateway`) | `admin-orders-local.gateway.ts` | none | **none** | `AdminOrdersFacade` (injects `AdminOrdersLocalGateway`) | MOCK-ONLY (no seam) |
|
||||
| Products | `admin-products-gateway.interface.ts` (`AdminProductsGateway`) | `admin-products-local.gateway.ts` | none | **none** | `AdminProductsFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Users | `admin-users-gateway.interface.ts` (`AdminUsersGateway`) | `admin-users-local.gateway.ts` | none | **none** | `AdminUsersFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Transactions | `admin-transactions-gateway.interface.ts` (`AdminTransactionsGateway`) | `admin-transactions-local.gateway.ts` | none | **none** | `AdminTransactionsFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Monitoring | `admin-monitoring-gateway.interface.ts` (`AdminMonitoringGateway`) | `admin-monitoring-local.gateway.ts` | none | **none** | `AdminMonitoringFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Moderation | `admin-moderation-gateway.interface.ts` (`AdminModerationGateway`) | `admin-moderation-local.gateway.ts` | none | **none** | `AdminModerationFacade` (injects local) | MOCK-ONLY (no seam) |
|
||||
| Customers | (no gateway of its own) | reuses `AdminOrdersLocalGateway` | none | **none** | `AdminCustomersFacade` (injects orders local) | MOCK-ONLY (derived) |
|
||||
| Analytics | (no gateway of its own) | reuses orders/products/moderation local + `ADMIN_CATEGORIES_GATEWAY` + `AdminDashboardFacade` | none | partial (categories token) | `AdminAnalyticsFacade` | MOCK-ONLY (derived) |
|
||||
|
||||
Gateway interface method contracts (the shapes a backend must satisfy):
|
||||
|
||||
- **`AdminCategoriesGateway`**: `loadCategories(filters)`, `loadCategory(id)`, `createCategory`,
|
||||
`updateCategory`, `deleteCategory`, `restoreCategory`, `isSlugTaken(slug,excludingId)`.
|
||||
- **`AdminDashboardMetricsGateway`**: `loadMetrics(): AdminDashboardMetrics`.
|
||||
- **`AdminOrdersGateway`**: `loadOrders(filters)`, `loadOrder(id)`, `updateStatus(id,status)`,
|
||||
`requestRefund(id)`, `addNote(id,note,internal)`, `archiveOrder`, `restoreOrder`, `deleteOrder`.
|
||||
- **`AdminProductsGateway`**: `loadProducts(filters)`, `loadProduct(id)`, `loadCategories()`,
|
||||
`createProduct`, `updateProduct`, `deleteProduct`, `duplicateProduct`, `archiveProduct`,
|
||||
`restoreProduct`.
|
||||
- **`AdminUsersGateway`**: `loadUsers`, `loadRoles`, `loadInvitations`, `loadSessions(userId)`,
|
||||
`loadAudit(userId)`, `setUserRole`, `setUserStatus`, `inviteUser(email,roleId,scope)`,
|
||||
`revokeInvitation`, `revokeSession`.
|
||||
- **`AdminTransactionsGateway`**: `loadTransactions(filters)`, `retryFailed(id)`,
|
||||
`setFraudFlag(id,flagged)`.
|
||||
- **`AdminMonitoringGateway`**: `loadEvents(filters)`, `loadQueues()`, `loadWebhooks()`.
|
||||
- **`AdminModerationGateway`**: `loadReviews(filters)`, `loadReview(id)`, `setReviewStatus`,
|
||||
`setReviewVisible`, `setReviewPinned`, `setReviewFeatured`, `addModeratorNote`, `deleteReview`,
|
||||
`loadReports()`, `setReportStatus(id,status)`.
|
||||
|
||||
Admin model files (all under `src/app/features/admin/<domain>/models/`) — see §23.
|
||||
|
||||
Note: `AdminRole` is defined **twice** with different meaning — `src/app/core/auth/models/
|
||||
permission.model.ts` (auth roles `Owner|Administrator|Editor|Support|ReadOnly`) vs
|
||||
`src/app/features/admin/users/models/admin-user.model.ts` (`AdminRole` interface {id,name,...}).
|
||||
Flag for backend/naming reconciliation.
|
||||
|
||||
Local gateways are localStorage / in-memory backed (facades also inject `LocalStorageService`
|
||||
for overlay persistence, e.g. orders/moderation/categories/products).
|
||||
|
||||
---
|
||||
|
||||
## 15. Domain: Media library
|
||||
|
||||
MOCK-SWAPPABLE via abstract-class token, no real impl.
|
||||
|
||||
- **Contract**: abstract class `MediaRepository` (`src/app/core/media/media-repository.ts`) —
|
||||
`list(params?)`, `upload(file,options?)`, `remove(id)`, `update(id,patch)`, `listFolders()`.
|
||||
- **Binding**: `app.config.ts` → `{ provide: MediaRepository, useClass: MockMediaRepository }`.
|
||||
- **Mock impl**: `MockMediaRepository` (`src/app/core/media/mock-media-repository.service.ts`,
|
||||
uses `HttpClient` to read seed assets). Also `MediaUsageService`
|
||||
(`src/app/core/media/media-usage.service.ts`).
|
||||
- **Facade**: `MediaLibraryFacade` (`src/app/features/backoffice/media/facade/media-library.facade.ts`),
|
||||
page `media-library-page.component.ts`.
|
||||
- **Models** (`src/app/core/media/models/media-asset.model.ts`): `MediaAsset`, `MediaAssetKind`,
|
||||
`MediaSort`, `MediaListParams`, `MediaUploadOptions`, `MediaListResult`.
|
||||
- Admin-auth interceptor already gates `/media/` paths (§3), anticipating a real media backend.
|
||||
|
||||
---
|
||||
|
||||
## 16. Domain: Content management / static pages
|
||||
|
||||
**LOCAL-ONLY** — operates on the already-loaded `BootstrapConfig.staticPages`, no dedicated
|
||||
backend calls. Publishing/writing bootstrap is not implemented client-side (FUTURE).
|
||||
|
||||
- **Facade**: `ContentManagementFacade` (`src/app/features/content-management/facade/content-management.facade.ts`)
|
||||
→ `ContentPageService` (`.../services/content-page.service.ts`). Public API: `pages(bootstrap)`,
|
||||
`hasSeoContent(page)`, `contentHealth(bootstrap)`, `resolvePage(bootstrap,keyOrSlug,locale)`,
|
||||
`validatePages(bootstrap)`, `toBootstrapRecord(bootstrap)`, `serializePages(pages)`,
|
||||
`normalizeSlug`.
|
||||
- `ContentPageService` maps between bootstrap `StaticPagesConfig` and the editor `ContentPage`
|
||||
view model (normalize / validate / `toBootstrapRecord`). This is the adapter.
|
||||
- **Models** (`src/app/features/content-management/models/`): `ContentPage`,
|
||||
`ContentPageTranslation`, `ContentPageSeoConfig`, `ContentPageStatus`,
|
||||
`ContentPageBootstrapInput` (`content-page.model.ts`); `LegalPageKey`, `LegalPageDefinition`
|
||||
(`legal-pages.model.ts`). Backend-shaped counterpart: `StaticPageConfig`,
|
||||
`StaticPagesConfig`, `ResolvedStaticPage`, `LocalizedHtmlContent`, `LocalizedTextContent`
|
||||
(`src/app/shared/models/config/static-page.model.ts`).
|
||||
- Consumers: `static-pages-editor.component.ts`, `page-editor.component.ts`,
|
||||
`static-page.component.ts` (`src/app/pages/static-page/`), resolved via
|
||||
`StaticPageResolverService`.
|
||||
|
||||
---
|
||||
|
||||
## 17. Domain: Project editor / builder
|
||||
|
||||
**LOCAL-ONLY** today — edits an in-memory `BootstrapConfig`, persists drafts to localStorage;
|
||||
no publish/save-to-backend HTTP call exists. A builder API is declared only as
|
||||
`BootstrapConfig.apiEndpoints.builder` (runtime-declared, FUTURE).
|
||||
|
||||
- **Facade**: `ProjectEditorFacade` (`src/app/features/project-editor/facade/project-editor.facade.ts`)
|
||||
— orchestrates undo/redo `History<BootstrapConfig>`, injects `ConfigService`,
|
||||
`ProjectEditorIoService` (JSON import/export of bootstrap), `ProjectEditorPreviewService`,
|
||||
`LocaleSyncService`, `PlatformRuntimeService`, `ProjectValidator`,
|
||||
`ProjectEditorDraftStorageService` (localStorage drafts), `EditorSchemaService`.
|
||||
- Services (`src/app/features/project-editor/services/`): `project-editor-io.service.ts`
|
||||
(`exportBootstrap`/`importBootstrap` = JSON.stringify/parse), `project-editor-draft-storage.service.ts`,
|
||||
`project-editor-preview.service.ts`, `project-validator.service.ts`, `locale-sync.service.ts`.
|
||||
Schema: `schema/editor-schema.service.ts`, `schema/field-schema.model.ts`, `schema/validators/`.
|
||||
- **Models**: `project-editor.model.ts` (`ProjectEditorState`, `ProjectEditorSectionId`,
|
||||
`ProjectEditorWidgetPreset`, `BuilderSectionStatus`), `builder/builder-groups.model.ts`.
|
||||
- Consumers: `project-editor-page.component.ts`, `homepage-section.component.ts`,
|
||||
`project-editor-nav.component.ts`. Also drives admin products/categories/dashboard facades
|
||||
(which inject `ProjectEditorFacade`).
|
||||
|
||||
---
|
||||
|
||||
## 18. Domain: Search
|
||||
|
||||
**LOCAL-ONLY orchestration over the product/category providers** — no dedicated search backend;
|
||||
`SearchFacade` composes `ProductFacade` + `CategoryFacade` results and manages history/trending/
|
||||
autocomplete/cache client-side.
|
||||
|
||||
- **Facade**: `SearchFacade` (`src/app/features/search/facade/search.facade.ts`) injects
|
||||
`ProductFacade`, `CategoryFacade`, `SearchAutocompleteService`, `SearchHistoryService`,
|
||||
`SearchTrendingService`, `SearchCacheService`, `SearchStore`, `TranslateService`.
|
||||
- Services (`src/app/features/search/services/`): `search-autocomplete.service.ts`,
|
||||
`search-history.service.ts` + `search-history.repository.ts` (interface
|
||||
`SearchHistoryRepository{load,save,clear}`, localStorage), `search-trending.service.ts`,
|
||||
`search-cache.service.ts`. Store: `store/search.store.ts`.
|
||||
- **Models**: `src/app/features/search/models/search.model.ts` (`SearchQuery`, `SearchResult<T>`,
|
||||
`SearchSuggestion`, `SearchFilterType`, `FilterGroup`, `FilterOption`, `SortOption`,
|
||||
`SearchHistory`, `SearchAnalyticsEvent`, `SearchNavigationTarget`), `search-state.model.ts`
|
||||
(`SearchState`). Duplicated under `src/app/core/search/models/`.
|
||||
- Underlying live traffic is `GET /searchitems` (§4) via `ProductFacade.searchProducts`.
|
||||
|
||||
---
|
||||
|
||||
## 19. Domain: User experience (wishlist/compare/etc.)
|
||||
|
||||
**LOCAL-ONLY** (guest-first). MOCK-SWAPPABLE token exists for a future authenticated backend.
|
||||
|
||||
- **Interface**: `UserExperienceRepository`
|
||||
(`src/app/core/user-experience/repositories/user-experience.repository.ts`).
|
||||
- **DI token**: `USER_EXPERIENCE_REPOSITORY`
|
||||
(`src/app/core/user-experience/user-experience-repository.token.ts`) → currently always
|
||||
`LocalUserExperienceRepository` (localStorage). Comment notes it "can be switched to
|
||||
authenticated repository later."
|
||||
- **Facade**: `UserExperienceFacade` (`src/app/facades/platform/user-experience.facade.ts`) —
|
||||
wishlist / compare / recently-viewed / saved-searches / continue-browsing, all signals.
|
||||
- **Models** (`src/app/core/user-experience/models/user-experience.model.ts`): `FavoriteItem`,
|
||||
`ComparedProduct`, `RecentlyViewedItem`, `SavedSearch`, `ContinueBrowsingState`. Config shape:
|
||||
`user-experience-config.model.ts` (limits, from bootstrap).
|
||||
|
||||
---
|
||||
|
||||
## 20. Domain: Diagnostics
|
||||
|
||||
**LOCAL-ONLY** — inspects runtime/bootstrap/widget state; the one live-ish probe is API ping.
|
||||
|
||||
- **Facade**: `DiagnosticsFacade` (`src/app/features/diagnostics/facade/diagnostics.facade.ts`)
|
||||
injects `ConfigService`, `TenantResolverService`, `PlatformRuntimeStateService`,
|
||||
`RuntimeDiagnosticsService`, `WidgetManifestService`, `WidgetRegistryService`,
|
||||
`RuntimeProviderStrategyService`, `DiagnosticsLoggerService`, `TranslateService`, `Router`.
|
||||
- Validators: `validators/runtime-diagnostics.validator.ts` (uses `HttpClient` for API health
|
||||
probe), `bootstrap-diagnostics.validator.ts`, `diagnostics-health-score.util.ts`.
|
||||
- **Models** (`src/app/features/diagnostics/models/diagnostics.model.ts`): `DiagnosticEntry`,
|
||||
`DiagnosticSeverity`, `DiagnosticsHealthSummary`, `DiagnosticsReport`.
|
||||
|
||||
---
|
||||
|
||||
## 21. Facade catalog
|
||||
|
||||
| Facade | File | Depends on | Consumed by (examples) |
|
||||
|---|---|---|---|
|
||||
| `ProductFacade` | `facades/platform/product.facade.ts` | `ProductDataService` → `PRODUCT_DATA_PROVIDER` | catalog/product containers, `SearchFacade` |
|
||||
| `CategoryFacade` | `facades/platform/category.facade.ts` | `CategoryService` → `CATEGORY_REPOSITORY` | catalog nav, `SearchFacade` |
|
||||
| `SearchFacade` | `features/search/facade/search.facade.ts` | ProductFacade, CategoryFacade, search services | search bar/pages |
|
||||
| `UserExperienceFacade` | `facades/platform/user-experience.facade.ts` | `USER_EXPERIENCE_REPOSITORY` | wishlist/compare UI |
|
||||
| `UiRuntimeFacade` | `facades/runtime/ui-runtime.facade.ts` | `ConfigService` | header/branding |
|
||||
| `WebsiteRuntimeFacade` | `facades/website/website-runtime.facade.ts` | config/page renderer | dynamic pages |
|
||||
| `AuthFacade` | `core/auth/services/auth-facade.service.ts` | AuthService, SessionService, PermissionService | login/guarded UI |
|
||||
| `MediaLibraryFacade` | `features/backoffice/media/facade/media-library.facade.ts` | `MediaRepository` | media page |
|
||||
| `ContentManagementFacade` | `features/content-management/facade/...` | `ContentPageService` (bootstrap) | content dashboard/editor |
|
||||
| `ProjectEditorFacade` | `features/project-editor/facade/...` | config + editor services (localStorage) | builder pages, admin facades |
|
||||
| `DiagnosticsFacade` | `features/diagnostics/facade/...` | runtime/config/widget services | diagnostics page |
|
||||
| `AdminCategoriesFacade` | `features/admin/categories/facade/...` | `ADMIN_CATEGORIES_GATEWAY`, ProjectEditorFacade | admin categories pages |
|
||||
| `AdminProductsFacade` | `features/admin/products/facade/...` | `AdminProductsLocalGateway`, ProjectEditorFacade | admin products pages |
|
||||
| `AdminOrdersFacade` | `features/admin/orders/facade/...` | `AdminOrdersLocalGateway` | admin orders pages |
|
||||
| `AdminUsersFacade` | `features/admin/users/facade/...` | `AdminUsersLocalGateway` | admin users pages |
|
||||
| `AdminTransactionsFacade` | `features/admin/transactions/facade/...` | `AdminTransactionsLocalGateway` | admin transactions pages |
|
||||
| `AdminMonitoringFacade` | `features/admin/monitoring/facade/...` | `AdminMonitoringLocalGateway` | admin monitoring page |
|
||||
| `AdminModerationFacade` | `features/admin/moderation/facade/...` | `AdminModerationLocalGateway` | moderation pages |
|
||||
| `AdminCustomersFacade` | `features/admin/customers/facade/...` | `AdminOrdersLocalGateway` (derives customers from orders) | customers pages |
|
||||
| `AdminAnalyticsFacade` | `features/admin/analytics/facade/...` | orders/products/moderation local + `ADMIN_CATEGORIES_GATEWAY` + `AdminDashboardFacade` | analytics page |
|
||||
| `AdminDashboardFacade` | `features/admin/dashboard/facade/...` | `ADMIN_DASHBOARD_METRICS_GATEWAY`, ProjectEditorFacade, AdminAuthService | admin dashboard |
|
||||
|
||||
`ProductFacade` public API: `getProducts, getProduct, getCategories, searchProducts,
|
||||
getFeaturedProducts, getLatestProducts, getProductsByCategory, getRelatedProducts, loadRating,
|
||||
loadReviews, loadQuestions, submitReview, submitQuestion, search(criteria), filter, sort,
|
||||
loadCatalog`. `CategoryFacade`: signals (`allCategories, categoryTree, rootCategories,
|
||||
selectedCategory, breadcrumb, children, loading, error`) + `loadCategories, selectCategory,
|
||||
getAllCategories, getCategoryTree, getRootCategories, getCategoryById, getBreadcrumb, getChildren`.
|
||||
`UserExperienceFacade`: `isInWishlist, toggleWishlist, clearWishlist, isInCompare, addToCompare,
|
||||
removeFromCompare, clearCompare, trackRecentlyViewed, saveSearch, removeSavedSearch,
|
||||
saveContinueBrowsing, getContinueBrowsing` + wishlist/compare signals & counts.
|
||||
|
||||
---
|
||||
|
||||
## 22. Gateway / provider master table
|
||||
|
||||
| Gateway/provider | Interface path | Mock/local impl | Real/API impl | DI token | Consuming facade(s) | Status |
|
||||
|---|---|---|---|---|---|---|
|
||||
| ConfigProvider | `core/config/config-provider.interface.ts` | `core/bootstrap/providers/mock-bootstrap.provider.ts` | `core/bootstrap/providers/api-bootstrap.provider.ts` | `CONFIG_PROVIDER` | UiRuntime, WebsiteRuntime, ProjectEditor, ContentMgmt, Diagnostics (via ConfigService) | LIVE (`GET /bootstrap`) |
|
||||
| ProductDataProvider | `core/products/providers/product-data-provider.interface.ts` | none bound | `core/products/providers/api-product-data.provider.ts` | `PRODUCT_DATA_PROVIDER` | ProductFacade | LIVE |
|
||||
| CategoryRepository | `core/categories/repositories/category.repository.ts` | none bound | `core/categories/repositories/api-category.repository.ts` | `CATEGORY_REPOSITORY` | CategoryFacade | LIVE |
|
||||
| BackofficeDataProvider | `core/backoffice/providers/backoffice-data-provider.interface.ts` | `mock-backoffice-data.provider.ts` | `api-backoffice-data.provider.ts` | `BACKOFFICE_DATA_PROVIDER` | storefront cards | LIVE (`/api/backoffice/*`) |
|
||||
| UserExperienceRepository | `core/user-experience/repositories/user-experience.repository.ts` | `local-user-experience.repository.ts` | none | `USER_EXPERIENCE_REPOSITORY` | UserExperienceFacade | LOCAL-ONLY |
|
||||
| MediaRepository | `core/media/media-repository.ts` (abstract class) | `core/media/mock-media-repository.service.ts` | none | `MediaRepository` class (app.config.ts) | MediaLibraryFacade | MOCK-SWAPPABLE |
|
||||
| SearchHistoryRepository | `features/search/services/search-history.repository.ts` | (localStorage impl) | none | (injected concretely) | SearchFacade (via SearchHistoryService) | LOCAL-ONLY |
|
||||
| AdminCategoriesGateway | `features/admin/categories/services/admin-categories-gateway.interface.ts` | `admin-categories-local.gateway.ts` | `admin-categories-api.gateway.ts` | `ADMIN_CATEGORIES_GATEWAY` | AdminCategoriesFacade, AdminAnalyticsFacade | MOCK-SWAPPABLE (real impl exists) |
|
||||
| AdminDashboardMetricsGateway | `features/admin/dashboard/services/admin-dashboard-metrics.gateway.interface.ts` | `admin-dashboard-metrics.local.gateway.ts` | none | `ADMIN_DASHBOARD_METRICS_GATEWAY` | AdminDashboardFacade | MOCK-SWAPPABLE (token only) |
|
||||
| AdminOrdersGateway | `features/admin/orders/services/admin-orders-gateway.interface.ts` | `admin-orders-local.gateway.ts` | none | **none** | AdminOrdersFacade, AdminCustomersFacade, AdminAnalyticsFacade | MOCK-ONLY (no seam) |
|
||||
| AdminProductsGateway | `features/admin/products/services/admin-products-gateway.interface.ts` | `admin-products-local.gateway.ts` | none | **none** | AdminProductsFacade, AdminAnalyticsFacade | MOCK-ONLY (no seam) |
|
||||
| AdminUsersGateway | `features/admin/users/services/admin-users-gateway.interface.ts` | `admin-users-local.gateway.ts` | none | **none** | AdminUsersFacade | MOCK-ONLY (no seam) |
|
||||
| AdminTransactionsGateway | `features/admin/transactions/services/admin-transactions-gateway.interface.ts` | `admin-transactions-local.gateway.ts` | none | **none** | AdminTransactionsFacade | MOCK-ONLY (no seam) |
|
||||
| AdminMonitoringGateway | `features/admin/monitoring/services/admin-monitoring-gateway.interface.ts` | `admin-monitoring-local.gateway.ts` | none | **none** | AdminMonitoringFacade | MOCK-ONLY (no seam) |
|
||||
| AdminModerationGateway | `features/admin/moderation/services/admin-moderation-gateway.interface.ts` | `admin-moderation-local.gateway.ts` | none | **none** | AdminModerationFacade, AdminAnalyticsFacade | MOCK-ONLY (no seam) |
|
||||
| (Auth session) | — (`TelegramSessionApiService`) | mock via `mockDataInterceptor` | `services/telegram-session-api.service.ts` | n/a (concrete) | AuthService, AdminAuthService, AuthFacade | LIVE |
|
||||
| (Ed25519 admin auth) | — (`AuthApiService`) | none | `core/auth/services/auth-api.service.ts` | n/a (concrete) | AuthService (Ed25519 flow) | LIVE wiring, backend absent |
|
||||
|
||||
---
|
||||
|
||||
## 23. Model / DTO catalog
|
||||
|
||||
Grouped by boundary role. B = backend-shaped/wire DTO, V = frontend view model, C = bootstrap
|
||||
config shape. Adapter column names the mapper if distinct.
|
||||
|
||||
### Core wire DTOs / domain (B)
|
||||
- `Item` + supporting (`src/app/models/item.model.ts`) — **primary product wire shape**; adapter
|
||||
`ApiService.normalizeItem()`.
|
||||
- `Category`, `Subcategory` (`src/app/models/category.model.ts`) — legacy category wire; adapter
|
||||
`ApiService.normalizeCategory()`.
|
||||
- `CategoryDto`, `CategoryNameDto` (`src/app/core/categories/dto/category.dto.ts`) — clean-stack
|
||||
wire DTO; adapter `CategoryMapper`.
|
||||
- `Region`, `GeoIpResponse` (`src/app/models/location.model.ts`).
|
||||
- Payment/order DTOs inline in `src/app/services/api.service.ts`: `QrCreateRequest`,
|
||||
`QrCreateResponse`, `CartPaymentRequest`, `CreateOrderRequest`, `CreateOrderResponse`,
|
||||
`QrDynamicStatusResponse`.
|
||||
- Auth: `AuthSession`, `WebSessionStart` (`src/app/models/auth.model.ts`); `AuthChallenge`,
|
||||
`VerifySignatureRequest`, `AuthTokenPair`, `RefreshTokenRequest`, `JwtClaims`
|
||||
(`src/app/core/auth/models/auth-api.model.ts`).
|
||||
|
||||
### Domain / view models (V)
|
||||
- Products: `Product`(=Item alias), `ProductListQuery`, `ProductSearchQuery`, `ProductListResult`,
|
||||
`ProductFilters`, `RelatedProductsQuery`, `RelatedProductCollection`, `ProductVariantSelection`
|
||||
(`core/products/models/product-domain.model.ts`).
|
||||
- Engagement: `Review`, `Answer`, `Question`, `RatingSummary`, `RatingDistributionEntry`,
|
||||
`EngagementListQuery`, `EngagementListResult<T>`, `SubmitReviewInput`, `SubmitQuestionInput`
|
||||
(`core/products/models/product-engagement.model.ts`).
|
||||
- Catalog experience: `SearchCriteria`, `FilterDefinition`, `FilterOption`, `SortDefinition`,
|
||||
`CatalogView`, `SearchResult` (`core/products/models/catalog-experience.model.ts`);
|
||||
catalog state (`features/website/catalog/models/catalog-state.model.ts`).
|
||||
- Category domain: `Category`, `CategoryTranslation` (`core/categories/models/category-domain.model.ts`).
|
||||
- Media: `MediaAsset` + params/results (`core/media/models/media-asset.model.ts`).
|
||||
- User experience: `FavoriteItem`, `ComparedProduct`, `RecentlyViewedItem`, `SavedSearch`,
|
||||
`ContinueBrowsingState` (`core/user-experience/models/user-experience.model.ts`).
|
||||
- Search: `search.model.ts` + `search-state.model.ts` (`features/search/models/`, dup in `core/search/models/`).
|
||||
- Content: `ContentPage`, `ContentPageTranslation`, `ContentPageSeoConfig`, `ContentPageStatus`,
|
||||
`ContentPageBootstrapInput`, `LegalPageKey`, `LegalPageDefinition`
|
||||
(`features/content-management/models/`); adapter `ContentPageService`.
|
||||
- Project editor: `ProjectEditorState`, `ProjectEditorSectionId`, `ProjectEditorWidgetPreset`,
|
||||
`BuilderSectionStatus` (`features/project-editor/models/`), `builder-groups.model.ts`.
|
||||
- Diagnostics: `DiagnosticEntry`, `DiagnosticsHealthSummary`, `DiagnosticsReport`
|
||||
(`features/diagnostics/models/diagnostics.model.ts`).
|
||||
- Widgets: contracts in `src/app/widgets/contracts/*` and renderer `*.model.ts` (see §13).
|
||||
|
||||
### Admin models (V, all under `features/admin/<domain>/models/`)
|
||||
- `admin-order.model.ts`: `AdminOrder`, `AdminOrderCustomer`, `AdminOrderPayment`,
|
||||
`AdminOrderShipping`, `AdminOrderItem`, `AdminOrderTimelineEntry`, `AdminOrderStatus`,
|
||||
`AdminOrderPaymentStatus`, `AdminOrderTimelineEventKey`, `AdminOrderListFilters`,
|
||||
`AdminOrdersListResult`.
|
||||
- `admin-product.model.ts`: `AdminProduct` (+ `AdminProductMedia`, `AdminProductSpecification`,
|
||||
`AdminProductVariant(Price)`, `AdminProductVariantAttributeDef`, `AdminProductAttribute`,
|
||||
`AdminProductTranslation`, `AdminProductSeo`, `AdminProductReview`, `AdminProductQuestion`),
|
||||
`AdminProductListFilters`, `AdminProductsListResult`, `AdminProductCategoryOption`, status/sort/mode types.
|
||||
- `admin-category.model.ts`: `AdminCategory`, `AdminCategoryTranslation`, `AdminCategorySeo`,
|
||||
`AdminCategoryAttribute`, `AdminCategoryListFilters`, status/mode types.
|
||||
- `admin-user.model.ts`: `AdminUser`, `AdminRole`, `AdminInvitation`, `AdminSession`,
|
||||
`AdminUserAuditEntry`, scope/status/invitation-status types.
|
||||
- `admin-transaction.model.ts`: `AdminTransaction`, `AdminTransactionAuditEntry`,
|
||||
`AdminTransactionListFilters`, `AdminTransactionsListResult`, type/status types.
|
||||
- `admin-monitoring.model.ts`: `AdminMonitoringEvent`, `AdminMonitoringEventFilters`,
|
||||
`AdminQueue`, `AdminWebhookDelivery`, category/level/queue/webhook status types.
|
||||
- `admin-review.model.ts`: `AdminReview`, `AdminReviewTimelineEntry`, `AdminReviewListFilters`,
|
||||
`AdminReviewsListResult`, status/timeline types.
|
||||
- `admin-report.model.ts`: `AdminReport`, `AdminReportTargetType`, `AdminReportStatus`.
|
||||
- `admin-customer.model.ts`: `AdminCustomer`.
|
||||
- `admin-analytics.model.ts`: `AdminAnalyticsSummary`, `AdminAnalyticsSeriesPoint`,
|
||||
`AdminAnalyticsTopProduct`, `AdminLowStockProduct`, `AdminRecentActivityEntry`,
|
||||
`AdminMarketplaceHealthCheck`, `AdminProductAnalytics(Row)`, `AdminCustomerAnalytics`,
|
||||
`AdminRecommendationCard`, date-range/severity/health types.
|
||||
- `admin-dashboard.model.ts`: `AdminDashboardMetrics`, `AdminDashboardCardState<T>`,
|
||||
`AdminDashboardQuickAction(Id)`, `AdminDashboardActivityEntry`, `AdminDashboardHealthCheck`,
|
||||
`AdminDashboardHomeHealthCheck`, `AdminDashboardDraftField`, `AdminDashboardShortcut`, status types.
|
||||
- Shell: `features/admin/shell/admin-nav.model.ts`.
|
||||
|
||||
### Bootstrap config shapes (C)
|
||||
All under `src/app/shared/models/config/` — see §6 for the full list (24 files + barrel).
|
||||
|
||||
---
|
||||
|
||||
## 24. Endpoint URL literals found in code
|
||||
|
||||
Marketplace API (relative to base): `/ping`, `/bootstrap`, `/category`, `/category/{id}`,
|
||||
`/items/{id}`, `/items/randomitems`, `/searchitems`, `/cart`, `/orders`, `/purchase-email`,
|
||||
`/regions`, `/websession/{sessionId}`, `/items/{id}/callback`, `/items/{id}/questiion`.
|
||||
|
||||
Backoffice storefront: `/api/backoffice/products`, `/api/backoffice/categories`.
|
||||
|
||||
Payment (`qrApiUrl` = `https://qr.vitanova.network/api`): `/qr`, `/qr/dynamic/{partnerId}/{qrId}`,
|
||||
`/card/{partnerId}/{orderId}`. Const partner id `web-97ec-9c57-4dde-9037-3a68f7f83750`.
|
||||
|
||||
Session auth (`authApiUrl`): `/users/sessions`, `/users/sessions/{id}`.
|
||||
|
||||
Ed25519 admin auth (`authApiUrl`): `/api/admin/auth/challenge|verify|refresh|logout`
|
||||
(not implemented server-side).
|
||||
|
||||
Static assets (not backend): `/assets/mock/bootstrap/bootstrap.json`,
|
||||
`/assets/mock/bootstrap/widget-manifest.json`.
|
||||
|
||||
External (not this platform): `http://ip-api.com/json/...` (geo-IP),
|
||||
`https://api.qrserver.com/v1/create-qr-code/...` (QR image), `https://t.me/{bot}`,
|
||||
`tg://resolve?...`.
|
||||
|
||||
`mockDataInterceptor` URL matchers (mock mode only): `/ping`, `/users/sessions[/{id}]`,
|
||||
`/category`, `/category/{id}`, `/items/{id}`, `/searchitems`, `/randomitems`, `/cart`,
|
||||
`/websession/{id}[/qr]`, `/qr`, `/items/{id}/callback`, `/purchase-email`, `/qr/payment/{id}`.
|
||||
|
||||
**No literal `/admin/*`, `/builder/*`, or per-admin-domain backoffice CRUD paths exist in code.**
|
||||
Those live only as `apiEndpoints.{builder,backoffice}` records inside the runtime bootstrap
|
||||
document, and admin gateways are in-memory (they never construct a URL). Any concrete admin CRUD
|
||||
path is therefore a proposal, not a verified literal — consistent with `docs/BACKEND_API.md`
|
||||
Assumption #2.
|
||||
|
||||
The admin-auth-headers interceptor gates these path **segments** (anticipatory, not called yet):
|
||||
`/admin/`, `/backoffice/`, `/builder/`, `/media/`.
|
||||
|
||||
---
|
||||
|
||||
## 25. Cross-check against existing docs
|
||||
|
||||
Skimmed: `docs/BACKEND_API.md` (canonical master spec, CURRENT/PLANNED/FUTURE tagging),
|
||||
`docs/AUTH.md`, `docs/ADMIN.md`, `docs/BACKEND_API_REMAINING_WORK.md`,
|
||||
`docs/architecture/foundation/**`, `docs/backend/BACKEND-INTEGRATION.md`.
|
||||
|
||||
Agreements (preserve these conventions downstream):
|
||||
- `docs/BACKEND_API.md` already uses `GET /bootstrap`, the `*LocalGateway` → `*ApiGateway`
|
||||
rebind pattern, and frozen auth/payment (ADR-010). Its CURRENT/PLANNED/FUTURE tagging maps
|
||||
cleanly onto LIVE / MOCK-SWAPPABLE / MOCK-ONLY here.
|
||||
- Assumption #2 (builder/backoffice paths are proposals, not literals) is confirmed by code.
|
||||
- `submitQuestion` typo `questiion` and `callback` review path confirmed against code.
|
||||
|
||||
Discrepancies / things to flag for a human:
|
||||
1. **`docs/BACKEND_API.md` PLANNED framing implies every admin domain is a token rebind.**
|
||||
In code, only `ADMIN_CATEGORIES_GATEWAY` and `ADMIN_DASHBOARD_METRICS_GATEWAY` are
|
||||
token-bound. Orders, products, users, transactions, monitoring, moderation (and derived
|
||||
customers/analytics) inject the concrete `*LocalGateway` directly — no seam. A backend
|
||||
integration for those requires adding a token first. This should be reconciled in the docs.
|
||||
2. **Only one real admin API impl exists** (`AdminCategoriesApiGateway`). Everything else admin
|
||||
is mock. Docs that describe admin endpoints as "PLANNED, served by local gateway" are
|
||||
accurate in spirit but the swap ergonomics differ per domain (see #1).
|
||||
3. **Duplicate `Category` types** (`src/app/models/category.model.ts` vs
|
||||
`core/categories/models/category-domain.model.ts`) and **duplicate `AdminRole`**
|
||||
(auth `permission.model.ts` string-union vs users `admin-user.model.ts` interface) — naming
|
||||
collisions a backend/contract author should be warned about.
|
||||
4. **Duplicate search models** under `features/search/models/` and `core/search/models/`.
|
||||
5. **Content-management & project-editor "save/publish" has no client HTTP call.** Docs that
|
||||
imply a builder publish endpoint should tag it FUTURE — there is no `PUT /bootstrap` or
|
||||
builder-write call anywhere in code today; changes live in localStorage drafts + in-memory
|
||||
bootstrap only.
|
||||
6. `PRODUCT_DATA_PROVIDER` / `CATEGORY_REPOSITORY` token factories return the Api provider even
|
||||
in `mock` mode (no mock class bound) — so `useMockData` does NOT mock products/categories at
|
||||
the provider layer; mocking there relies entirely on `mockDataInterceptor`. Worth noting if a
|
||||
doc claims a mock product provider exists.
|
||||
|
||||
---
|
||||
|
||||
_Generated from source on branch `B2B`. Every path above is repo-relative to
|
||||
`F:\dx\remote\marketplaces\`._
|
||||
Reference in New Issue
Block a user