fix(catalog): route Hyundai/Kia/Nissan to de account (licensed; tr is demo)

Verified on dev: the tr account serves NOT_LOGGED_IN_DEMO for Hyundai/Kia/Nissan
parts (browse-only), while the de (account 2) subscription is licensed and returns
real data (de: 266 Hyundai / 169 Kia models, non-demo, ~88/64 drill rows). Route
every P4 fetch for these brands to de in fetchP4Page (+ resolveAccount), and ensure
the de service token is authorized. Existing tr-coded catalog_vehicles must be
re-seeded so they carry de catalog codes (ops step on deploy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-05 16:36:54 +03:00
parent a101fe81c2
commit 3163cf8a3e

View File

@@ -46,6 +46,11 @@ export interface P4BrandHooks {
parseVehicleInfo?(html: string, vin: string): P4VehicleInfo | null;
}
// Brands whose parts are licensed only on the de (account 2) PL24 subscription —
// the tr account serves a NOT_LOGGED_IN_DEMO page for these. All P4 fetches for
// these services are routed to de (see fetchP4Page + resolveAccount).
const HYUNDAI_KIA_NISSAN_DE = new Set(["hyundai_parts", "kia_parts", "nissan_parts"]);
@Injectable()
export class PL24FordLegacyService {
private readonly logger = new Logger(PL24FordLegacyService.name);
@@ -148,37 +153,6 @@ export class PL24FordLegacyService {
this.logger.log(`Ford legacy: fetching sub-groups from ${linkPath}`);
// TEMP DE-PROBE: run the FULL de-account flow with de's OWN catalog codes
// (de group.action → de vehicle.action → de drill) to definitively test
// whether the de account is licensed (non-demo) for Hyundai/Kia/Nissan parts.
if (["hyundai_parts", "kia_parts", "nissan_parts"].includes(serviceName)) {
try {
const cfg = getServiceConfig(serviceName);
const bp = cfg
? `${cfg.basePath}/${serviceName}`
: `/hyundai-kia-automotive-group/${serviceName}`;
await this.authService.authorizeServiceForAccount(serviceName, "de");
const gh = await this.fetchP4Page(`${this.baseUrl}${bp}/group.action?lang=tr`, serviceName, true, "de");
const ghDemo = !!gh && gh.includes("NOT_LOGGED_IN_DEMO");
const mu = gh ? this.extractModeUpdsFromHtml(gh) : { mode: "", upds: "" };
const vh = await this.fetchP4Page(`${this.baseUrl}${bp}/vehicle.action?lang=tr&startup=true&mode=${mu.mode}&upds=${mu.upds}`, serviceName, true, "de");
const vhDemo = !!vh && vh.includes("NOT_LOGGED_IN_DEMO");
const rows = vh ? this.parseHyundaiKiaCatalogRows(vh) : [];
this.logger.warn(`DE-FULL ${serviceName}: groupDemo=${ghDemo} vehDemo=${vhDemo} deRows=${rows.length} first=${JSON.stringify(rows[0])}`);
if (rows.length) {
const mu2 = this.extractModeUpdsFromHtml(vh as string);
const em = mu2.mode || mu.mode;
const eu = mu2.upds || mu.upds;
const du = `${this.baseUrl}${bp}/group.action?catalog=${encodeURIComponent(rows[0].id)}&lang=tr&localMarketOnly=true&mainGroup=EN&spec=e30%3D&startup=false&mode=${em}&upds=${eu}`;
const dh = await this.fetchP4Page(du, serviceName, true, "de");
const dDemo = !!dh && dh.includes("NOT_LOGGED_IN_DEMO");
this.logger.warn(`DE-FULL ${serviceName} drill cat=${rows[0].id}: demo=${dDemo} tcRows=${dh ? (dh.match(/tc-data-row/g) || []).length : 0} len=${dh?.length}`);
}
} catch (e) {
this.logger.warn(`DE-FULL ${serviceName} probe failed: ${(e as Error).message}`);
}
}
try {
const account = await this.resolveAccount(userId, serviceName);
const html = await this.fetchP4Page(linkPath, serviceName, false, account);
@@ -397,36 +371,6 @@ export class PL24FordLegacyService {
this.logger.log(`Ford legacy: fetching parts from ${linkPath}`);
// TEMP DE-PROBE: full de-account flow with de's OWN catalog codes to test
// whether de is licensed (non-demo) for Hyundai/Kia/Nissan parts.
if (["hyundai_parts", "kia_parts", "nissan_parts"].includes(serviceName)) {
try {
const cfg = getServiceConfig(serviceName);
const bp = cfg
? `${cfg.basePath}/${serviceName}`
: `/hyundai-kia-automotive-group/${serviceName}`;
await this.authService.authorizeServiceForAccount(serviceName, "de");
const gh = await this.fetchP4Page(`${this.baseUrl}${bp}/group.action?lang=tr`, serviceName, true, "de");
const ghDemo = !!gh && gh.includes("NOT_LOGGED_IN_DEMO");
const mu = gh ? this.extractModeUpdsFromHtml(gh) : { mode: "", upds: "" };
const vh = await this.fetchP4Page(`${this.baseUrl}${bp}/vehicle.action?lang=tr&startup=true&mode=${mu.mode}&upds=${mu.upds}`, serviceName, true, "de");
const vhDemo = !!vh && vh.includes("NOT_LOGGED_IN_DEMO");
const rows = vh ? this.parseHyundaiKiaCatalogRows(vh) : [];
this.logger.warn(`DE-FULL ${serviceName}: groupDemo=${ghDemo} vehDemo=${vhDemo} deRows=${rows.length} first=${JSON.stringify(rows[0])}`);
if (rows.length) {
const mu2 = this.extractModeUpdsFromHtml(vh as string);
const em = mu2.mode || mu.mode;
const eu = mu2.upds || mu.upds;
const du = `${this.baseUrl}${bp}/group.action?catalog=${encodeURIComponent(rows[0].id)}&lang=tr&localMarketOnly=true&mainGroup=EN&spec=e30%3D&startup=false&mode=${em}&upds=${eu}`;
const dh = await this.fetchP4Page(du, serviceName, true, "de");
const dDemo = !!dh && dh.includes("NOT_LOGGED_IN_DEMO");
this.logger.warn(`DE-FULL ${serviceName} drill cat=${rows[0].id}: demo=${dDemo} tcRows=${dh ? (dh.match(/tc-data-row/g) || []).length : 0} len=${dh?.length}`);
}
} catch (e) {
this.logger.warn(`DE-FULL ${serviceName} probe failed: ${(e as Error).message}`);
}
}
try {
const account = await this.resolveAccount(userId, serviceName);
const isImageBoard = linkPath.includes("image-board.action");
@@ -1946,10 +1890,13 @@ export class PL24FordLegacyService {
* Fiat services always use 'de'; others use round-robin via Redis.
*/
private async resolveAccount(userId?: string, serviceName?: string): Promise<"tr" | "de"> {
// Fiat always needs de account
// Fiat always needs de account; Hyundai/Kia/Nissan parts are licensed only on de.
if (serviceName && ["fiatp_parts", "fiatt_parts"].includes(serviceName)) {
return "de";
}
if (serviceName && HYUNDAI_KIA_NISSAN_DE.has(serviceName)) {
return "de";
}
// No userId → tr (catalog browser / prefetch)
if (!userId) return "tr";
@@ -3266,6 +3213,15 @@ export class PL24FordLegacyService {
account: "tr" | "de" = "tr",
retried = false,
): Promise<string | null> {
// Hyundai/Kia/Nissan parts are licensed ONLY on the de account — the tr
// account returns a NOT_LOGGED_IN_DEMO page (browse-only). Route every P4
// fetch for these brands to de (which carries the Korean/Japanese license),
// and ensure the de service token is authorized. Verified on dev: de yields
// 266 Hyundai / 169 Kia models, non-demo, with real drill content.
if (HYUNDAI_KIA_NISSAN_DE.has(serviceName)) {
account = "de";
await this.authService.authorizeServiceForAccount(serviceName, "de");
}
// Some catalogs (Volvo vin-group.action) store hrefs relative to the catalog
// directory (e.g. "vin-group.action?group1=…"). Prefix the service basePath
// so `${baseUrl}${path}` doesn't collapse into "partslink24.comvin-group…".