perf(api): pre-warm PL24 auth on boot to kill the post-deploy cold-start
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

PL24 auth tokens (account JWT + per-service tokens) are cached in-memory, so a
(re)deploy clears them and the first VIN decode of each service-family pays the
~10s login + service-authorize handshake on the request path. Measured on dev: a
cold opel_parts decode took 12s vs ~2s once the auth was warm.

Add OnModuleInit to PL24AuthService that warms, in the background (fire-and-forget,
never blocking boot), both base account logins (the shared dominant cost) plus the
common legacy + top Turkish-market service tokens. allSettled throughout so a
slow/down PL24 degrades gracefully; gated on credential presence; PL24_PREWARM=false
disables. The pre-decode (search page) already hides this from users mid-session —
this closes the one remaining gap: the first dealer right after a deploy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 01:16:59 +03:00
parent c2960013f0
commit afc68fa2d7

View File

@@ -7,7 +7,7 @@
* - 'de' (de-708171): DataImpulse Germany proxy, Fiat + EUR prices
*/
import { Injectable, Logger, UnauthorizedException } from "@nestjs/common";
import { Injectable, Logger, type OnModuleInit, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PL24_ENDPOINTS } from "./pl24.constants";
import {
@@ -20,7 +20,7 @@ import {
} from "./pl24.types";
@Injectable()
export class PL24AuthService {
export class PL24AuthService implements OnModuleInit {
private readonly logger = new Logger(PL24AuthService.name);
// ── Account 1 (tr-903645) ──────────────────────────────────────────────────
@@ -64,6 +64,61 @@ export class PL24AuthService {
}
}
onModuleInit(): void {
// In-memory auth tokens are cleared on every (re)deploy, so the first VIN
// decode afterwards otherwise pays the ~10s PL24 login + service-authorize
// handshake on the request path. Warm it in the background so users never do.
// Fire-and-forget — never block or fail boot; set PL24_PREWARM=false to disable.
if (process.env.PL24_PREWARM === "false") return;
if (!this.companyCode || !this.username || !this.password) return;
void this.prewarm().catch((err) =>
this.logger.warn(`PL24 auth pre-warm error: ${(err as Error).message}`),
);
}
/**
* Pre-fill the in-memory auth caches: the base account logins (the shared,
* dominant cost) plus the common legacy + top Turkish-market service tokens.
* Idempotent — the underlying methods cache, so this just populates the cache.
* Uses allSettled throughout: a slow/down PL24 degrades gracefully (logged).
*/
async prewarm(): Promise<void> {
const t0 = Date.now();
// 1) Base account JWT + session cookie. login() for "tr" (tokenData) and "de"
// (tokenData2) are independent; the session cookie is warmed as a side effect.
const baseResults = await Promise.allSettled([
this.getAccessTokenForAccount("tr"),
this.companyCode2 ? this.getAccessTokenForAccount("de") : Promise.resolve(""),
]);
const baseOk = baseResults.filter((r) => r.status === "fulfilled").length;
// 2) Common legacy (Ford-legacy JWT flow) + top Turkish P5 service tokens.
// Only if the tr login succeeded, so we don't stampede concurrent logins.
const services = [
"opel_parts",
"fordt_parts",
"hyundai_parts",
"nissan_parts",
"volvo_parts",
"vw_parts",
"renault_parts",
"toyota_parts",
];
let svcOk = 0;
if (baseResults[0].status === "fulfilled") {
const svcResults = await Promise.allSettled(
services.map((svc) => this.authorizeServiceForAccount(svc, "tr")),
);
svcOk = svcResults.filter((r) => r.status === "fulfilled").length;
}
this.logger.log(
`PL24 auth pre-warm done in ${Date.now() - t0}ms (logins ${baseOk}/2, services ${svcOk}/${services.length}${
baseResults[0].status === "fulfilled" ? "" : " — services skipped: tr login failed"
})`,
);
}
// ═══════════════════════════════════════════════════════════════════════════
// ── Public: per-account API ──────────────────────────────────────────────
// ═══════════════════════════════════════════════════════════════════════════