Просмотр исходного кода

feat: add domestic voc api data mode

gangvy 1 месяц назад
Родитель
Сommit
1f9798860e

+ 24 - 0
BACKEND-INTEGRATION.md

@@ -0,0 +1,24 @@
+# Backend integration
+
+The bundled Demashi JSON remains the default so the reusable template can start without backend credentials. Deployments can switch all 29 pages to the independent backend without changing page components by injecting configuration before the Angular bundles load:
+
+```html
+<script>
+  window.__SAAS_VOC_CONFIG__ = {
+    domesticDatasetMode: 'api',
+    domesticVocPath: '/api/domestic-voc',
+    domesticWorkspaceId: 'demashi'
+  };
+</script>
+```
+
+API mode requests only:
+
+```text
+GET /api/domestic-voc/snapshot?workspaceId=demashi&platform=jd
+```
+
+Local `ng serve` forwards `/api/domestic-voc` to `http://127.0.0.1:4400`. Fmode and database credentials remain server-side.
+
+The API response must keep the `DomesticDataset` contract in `src/app/core/models/domestic.models.ts`. Missing reviews remain an empty array and must not be replaced by inferred VOC output.
+

+ 6 - 0
proxy.conf.json

@@ -4,5 +4,11 @@
     "secure": false,
     "changeOrigin": true,
     "logLevel": "debug"
+  },
+  "/api/domestic-voc": {
+    "target": "http://127.0.0.1:4400",
+    "secure": false,
+    "changeOrigin": true,
+    "logLevel": "debug"
   }
 }

+ 8 - 0
src/app/core/config/runtime-config.ts

@@ -3,6 +3,10 @@ export interface SaasVocRuntimeConfig {
   parseAppId: string;
   apiBaseUrl: string;
   vocEcommercePath: string;
+  domesticDatasetMode: 'case' | 'api';
+  domesticVocPath: string;
+  domesticWorkspaceId: string;
+  domesticPlatform: 'jd';
 }
 
 declare global {
@@ -19,4 +23,8 @@ export const RUNTIME_CONFIG: Readonly<SaasVocRuntimeConfig> = Object.freeze({
   parseAppId: browserConfig.parseAppId || 'saas-voc-local',
   apiBaseUrl: browserConfig.apiBaseUrl || '',
   vocEcommercePath: browserConfig.vocEcommercePath || '/api/voc-e-commerce',
+  domesticDatasetMode: browserConfig.domesticDatasetMode === 'api' ? 'api' : 'case',
+  domesticVocPath: browserConfig.domesticVocPath || '/api/domestic-voc',
+  domesticWorkspaceId: browserConfig.domesticWorkspaceId || 'demashi',
+  domesticPlatform: 'jd',
 });

+ 33 - 0
src/app/core/services/domestic-dataset.service.spec.ts

@@ -0,0 +1,33 @@
+import { buildDomesticDatasetUrl } from './domestic-dataset.service';
+
+describe('domestic dataset source selection', () => {
+  const baseConfig = {
+    apiBaseUrl: '',
+    domesticVocPath: '/api/domestic-voc',
+    domesticWorkspaceId: 'demashi',
+    domesticPlatform: 'jd' as const,
+  };
+
+  it('keeps the bundled Demashi case as the default source', () => {
+    expect(buildDomesticDatasetUrl({
+      ...baseConfig,
+      domesticDatasetMode: 'case',
+    })).toBe('assets/data/demashi-summary.json');
+  });
+
+  it('builds a same-origin backend snapshot URL in API mode', () => {
+    expect(buildDomesticDatasetUrl({
+      ...baseConfig,
+      domesticDatasetMode: 'api',
+    })).toBe('/api/domestic-voc/snapshot?workspaceId=demashi&platform=jd');
+  });
+
+  it('supports an explicit backend origin without duplicate slashes', () => {
+    expect(buildDomesticDatasetUrl({
+      ...baseConfig,
+      apiBaseUrl: 'https://voc.example.com/',
+      domesticVocPath: '/api/domestic-voc/',
+      domesticDatasetMode: 'api',
+    })).toBe('https://voc.example.com/api/domestic-voc/snapshot?workspaceId=demashi&platform=jd');
+  });
+});

+ 20 - 1
src/app/core/services/domestic-dataset.service.ts

@@ -3,13 +3,32 @@ import { Injectable, inject } from '@angular/core';
 import { Observable, map, shareReplay } from 'rxjs';
 import { DomesticDataset, DomesticProduct, DomesticReview } from '../models/domestic.models';
 import { ProductInfo } from '../../../modules/shared/interfaces/voc-insight-interfaces';
+import { RUNTIME_CONFIG, SaasVocRuntimeConfig } from '../config/runtime-config';
+
+export function buildDomesticDatasetUrl(
+  config: Pick<
+    SaasVocRuntimeConfig,
+    'apiBaseUrl' | 'domesticDatasetMode' | 'domesticVocPath' | 'domesticWorkspaceId' | 'domesticPlatform'
+  >,
+): string {
+  if (config.domesticDatasetMode === 'case') return 'assets/data/demashi-summary.json';
+
+  const baseUrl = config.apiBaseUrl.replace(/\/+$/, '');
+  const path = `/${config.domesticVocPath.replace(/^\/+|\/+$/g, '')}`;
+  const query = new URLSearchParams({
+    workspaceId: config.domesticWorkspaceId,
+    platform: config.domesticPlatform,
+  });
+  return `${baseUrl}${path}/snapshot?${query.toString()}`;
+}
 
 @Injectable({ providedIn: 'root' })
 export class DomesticDatasetService {
   private readonly http = inject(HttpClient);
+  private readonly datasetUrl = buildDomesticDatasetUrl(RUNTIME_CONFIG);
 
   readonly dataset$: Observable<DomesticDataset> = this.http
-    .get<DomesticDataset>('assets/data/demashi-summary.json')
+    .get<DomesticDataset>(this.datasetUrl)
     .pipe(shareReplay({ bufferSize: 1, refCount: false }));
 
   readonly products$: Observable<ProductInfo[]> = this.dataset$.pipe(