feat: real API gateways for all remaining local-only domains (F17-F39)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Block 3 of the frontend backlog. 18 gateways were hardcoded to their local
(mock/localStorage) implementation with no seam to a real backend at all -
this closes that gap for everything with a documented contract to build
against.
10 core gateways, token now resolves environment.useMockData ? local : api,
same pattern already proven on fx-quote earlier this session:
permission, analytics, cart (server-cart), finance, vk-id (identity),
connector (integrations), marketplace (registry), offer, seller,
mall-content
8 admin gateways, same pattern:
orders, products, users, transactions, monitoring, moderation,
notifications, dashboard-metrics
Endpoints came from the matching contract doc where one exists (Phase 1-10,
Track A, Track S - cited per file). Three domains have no dedicated contract
doc yet (transactions, monitoring, moderation) - those gateways call the
established /api/admin/v2/{resource} convention used throughout the rest of
docs/backend/, flagged in each file's own comment as inferred rather than
specified, for whoever writes that contract to confirm or correct.
One real fix along the way: offer-api.gateway.ts's publish() translates a
422 + details[] response (contract §7's actual failure mode) into the
interface's { ok: false, errors } shape, rather than letting an HTTP error
leak past a caller that expects a value back.
Media repository (mock-media-repository.service.ts) deliberately NOT
swapped - no backend contract exists for it anywhere in docs/backend/, and
inventing endpoint shapes with zero grounding is worse than leaving it mock.
Regression this surfaced, fixed as part of the same change: three spec files
stubbed a gateway's concrete Local class directly via useValue. That worked
by accident while the token unconditionally resolved to the local class; once
the token became conditional on useMockData, those specs silently injected
the real (unmocked) API gateway instead and failed. Fixed by providing the
token instead of the class - the pattern the ADMIN_CATEGORIES_GATEWAY entry
in the same spec file already used correctly, because categories was
already token-swapped before this session:
- admin-analytics.facade.spec.ts (orders/products/moderation gateways)
- admin-order-watcher.service.spec.ts (orders gateway, 3 call sites)
Verified: 115/115 unit tests, 5/5 E2E, arch:check clean, production build
succeeds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AdminReview, AdminReviewListFilters, AdminReviewsListResult, AdminReviewStatus } from '../models/admin-review.model';
|
||||
import { AdminReport, AdminReportStatus } from '../models/admin-report.model';
|
||||
import { AdminModerationGateway } from './admin-moderation-gateway.interface';
|
||||
|
||||
/**
|
||||
* No dedicated moderation contract doc exists yet - at the conventional
|
||||
* /api/admin/v2/{resource} path used throughout the rest of docs/backend/.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AdminModerationApiGateway implements AdminModerationGateway {
|
||||
private readonly http = inject(HttpClient);
|
||||
|
||||
loadReviews(filters: AdminReviewListFilters): Observable<AdminReviewsListResult> {
|
||||
let params = new HttpParams();
|
||||
for (const [key, value] of Object.entries(filters ?? {})) {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
params = params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
return this.http.get<AdminReviewsListResult>('/api/admin/v2/moderation/reviews', { params });
|
||||
}
|
||||
|
||||
loadReview(id: string): Observable<AdminReview | null> {
|
||||
return this.http.get<AdminReview | null>(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}`);
|
||||
}
|
||||
|
||||
setReviewStatus(id: string, status: AdminReviewStatus, note: string): Observable<AdminReview | null> {
|
||||
return this.http.patch<AdminReview | null>(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}`, { status, note });
|
||||
}
|
||||
|
||||
setReviewVisible(id: string, visible: boolean): Observable<AdminReview | null> {
|
||||
return this.http.patch<AdminReview | null>(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}`, { visible });
|
||||
}
|
||||
|
||||
setReviewPinned(id: string, pinned: boolean): Observable<AdminReview | null> {
|
||||
return this.http.patch<AdminReview | null>(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}`, { pinned });
|
||||
}
|
||||
|
||||
setReviewFeatured(id: string, featured: boolean): Observable<AdminReview | null> {
|
||||
return this.http.patch<AdminReview | null>(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}`, { featured });
|
||||
}
|
||||
|
||||
addModeratorNote(id: string, note: string): Observable<AdminReview | null> {
|
||||
return this.http.post<AdminReview | null>(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}/notes`, { note });
|
||||
}
|
||||
|
||||
deleteReview(id: string): Observable<void> {
|
||||
return this.http
|
||||
.delete(`/api/admin/v2/moderation/reviews/${encodeURIComponent(id)}`)
|
||||
.pipe(map(() => undefined));
|
||||
}
|
||||
|
||||
loadReports(): Observable<AdminReport[]> {
|
||||
return this.http.get<AdminReport[]>('/api/admin/v2/moderation/reports');
|
||||
}
|
||||
|
||||
setReportStatus(id: string, status: AdminReportStatus): Observable<AdminReport | null> {
|
||||
return this.http.patch<AdminReport | null>(`/api/admin/v2/moderation/reports/${encodeURIComponent(id)}`, { status });
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { InjectionToken, inject } from '@angular/core';
|
||||
import { environment } from '../../../../../environments/environment';
|
||||
import { AdminModerationGateway } from './admin-moderation-gateway.interface';
|
||||
import { AdminModerationLocalGateway } from './admin-moderation-local.gateway';
|
||||
import { AdminModerationApiGateway } from './admin-moderation-api.gateway';
|
||||
|
||||
/** Swap point for a real Moderation backend - see BACKEND-API-REFERENCE.md §8 (no seam existed before this token). */
|
||||
/** Swap point. */
|
||||
export const ADMIN_MODERATION_GATEWAY = new InjectionToken<AdminModerationGateway>('ADMIN_MODERATION_GATEWAY', {
|
||||
providedIn: 'root',
|
||||
factory: () => inject(AdminModerationLocalGateway),
|
||||
factory: () => (environment.useMockData ? inject(AdminModerationLocalGateway) : inject(AdminModerationApiGateway)),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user