feat: add Pcat catalog browsing — import pc dump, API endpoints, frontend routes
Some checks failed
CI / Lint, Typecheck, Test & Build (push) Has been cancelled
Some checks failed
CI / Lint, Typecheck, Test & Build (push) Has been cancelled
Import parts-catalogs.com dump into pc schema (72 catalogs, 2788 models, 134K groups). Add pcat-catalog service/controller with Redis caching. Create frontend routes for catalog → model → group navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,14 +3,16 @@ import { CatalogController } from "./catalog.controller";
|
||||
import { CatalogService } from "./catalog.service";
|
||||
import { EmexCatalogController } from "./emex-catalog.controller";
|
||||
import { EmexCatalogService } from "./emex-catalog.service";
|
||||
import { PcatCatalogController } from "./pcat-catalog.controller";
|
||||
import { PcatCatalogService } from "./pcat-catalog.service";
|
||||
import { PL24Module } from "../integrations/pl24/pl24.module";
|
||||
import { SubscriptionsModule } from "../subscriptions/subscriptions.module";
|
||||
import { StorageModule } from "../storage/storage.module";
|
||||
|
||||
@Module({
|
||||
imports: [PL24Module, SubscriptionsModule, StorageModule],
|
||||
controllers: [CatalogController, EmexCatalogController],
|
||||
providers: [CatalogService, EmexCatalogService],
|
||||
exports: [CatalogService, EmexCatalogService],
|
||||
controllers: [CatalogController, EmexCatalogController, PcatCatalogController],
|
||||
providers: [CatalogService, EmexCatalogService, PcatCatalogService],
|
||||
exports: [CatalogService, EmexCatalogService, PcatCatalogService],
|
||||
})
|
||||
export class CatalogModule {}
|
||||
|
||||
25
apps/api/src/catalog/pcat-catalog.controller.ts
Normal file
25
apps/api/src/catalog/pcat-catalog.controller.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Controller, Get, Param, Query } from "@nestjs/common";
|
||||
import { PcatCatalogService } from "./pcat-catalog.service";
|
||||
|
||||
@Controller("catalog/pcat")
|
||||
export class PcatCatalogController {
|
||||
constructor(private pcatCatalogService: PcatCatalogService) {}
|
||||
|
||||
@Get("catalogs")
|
||||
getCatalogs() {
|
||||
return this.pcatCatalogService.getCatalogs();
|
||||
}
|
||||
|
||||
@Get("catalogs/:catalogId/models")
|
||||
getModels(@Param("catalogId") catalogId: string) {
|
||||
return this.pcatCatalogService.getModels(catalogId);
|
||||
}
|
||||
|
||||
@Get("catalogs/:catalogId/groups")
|
||||
getGroups(
|
||||
@Param("catalogId") catalogId: string,
|
||||
@Query("parentId") parentId?: string,
|
||||
) {
|
||||
return this.pcatCatalogService.getGroups(catalogId, parentId);
|
||||
}
|
||||
}
|
||||
132
apps/api/src/catalog/pcat-catalog.service.ts
Normal file
132
apps/api/src/catalog/pcat-catalog.service.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { Inject, Injectable, Logger } from "@nestjs/common";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { DATABASE, Database } from "../database/database.provider";
|
||||
import { RedisService } from "../redis/redis.service";
|
||||
|
||||
export interface PcatCatalogDto {
|
||||
id: string;
|
||||
name: string;
|
||||
modelsCount: number;
|
||||
}
|
||||
|
||||
export interface PcatModelDto {
|
||||
id: string;
|
||||
catalogId: string;
|
||||
name: string;
|
||||
imgUrl: string | null;
|
||||
}
|
||||
|
||||
export interface PcatGroupDto {
|
||||
id: string;
|
||||
catalogId: string;
|
||||
parentId: string | null;
|
||||
name: string;
|
||||
imgUrl: string | null;
|
||||
hasSubgroups: boolean;
|
||||
hasParts: boolean;
|
||||
}
|
||||
|
||||
const CACHE_TTL = {
|
||||
catalogs: 86400, // 24h
|
||||
models: 43200, // 12h
|
||||
groups: 21600, // 6h
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PcatCatalogService {
|
||||
private readonly logger = new Logger(PcatCatalogService.name);
|
||||
|
||||
constructor(
|
||||
@Inject(DATABASE) private db: Database,
|
||||
private redis: RedisService,
|
||||
) {}
|
||||
|
||||
async getCatalogs(): Promise<PcatCatalogDto[]> {
|
||||
const cacheKey = "pcat:catalogs";
|
||||
const cached = await this.redis.getJson<PcatCatalogDto[]>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const rows = await this.db.execute<{
|
||||
id: string;
|
||||
name: string;
|
||||
models_count: number;
|
||||
}>(sql`SELECT id, name, models_count FROM pc.catalogs WHERE is_active = true ORDER BY name`);
|
||||
|
||||
const result: PcatCatalogDto[] = rows.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
modelsCount: r.models_count ?? 0,
|
||||
}));
|
||||
|
||||
await this.redis.setJson(cacheKey, result, CACHE_TTL.catalogs);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getModels(catalogId: string): Promise<PcatModelDto[]> {
|
||||
const cacheKey = `pcat:models:${catalogId}`;
|
||||
const cached = await this.redis.getJson<PcatModelDto[]>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const rows = await this.db.execute<{
|
||||
id: string;
|
||||
catalog_id: string;
|
||||
name: string;
|
||||
img_url: string | null;
|
||||
}>(
|
||||
sql`SELECT id, catalog_id, name, img_url FROM pc.models WHERE catalog_id = ${catalogId} AND is_active = true ORDER BY name`,
|
||||
);
|
||||
|
||||
const result: PcatModelDto[] = rows.map((r) => ({
|
||||
id: r.id,
|
||||
catalogId: r.catalog_id,
|
||||
name: r.name,
|
||||
imgUrl: r.img_url,
|
||||
}));
|
||||
|
||||
await this.redis.setJson(cacheKey, result, CACHE_TTL.models);
|
||||
return result;
|
||||
}
|
||||
|
||||
async getGroups(catalogId: string, parentId?: string): Promise<PcatGroupDto[]> {
|
||||
const cacheKey = `pcat:groups:${catalogId}:${parentId || "root"}`;
|
||||
const cached = await this.redis.getJson<PcatGroupDto[]>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const rows = parentId
|
||||
? await this.db.execute<{
|
||||
id: string;
|
||||
catalog_id: string;
|
||||
parent_id: string | null;
|
||||
name: string;
|
||||
img_url: string | null;
|
||||
has_subgroups: boolean;
|
||||
has_parts: boolean;
|
||||
}>(
|
||||
sql`SELECT id, catalog_id, parent_id, name, img_url, has_subgroups, has_parts FROM pc.groups WHERE catalog_id = ${catalogId} AND parent_id = ${parentId} ORDER BY sort_order, name`,
|
||||
)
|
||||
: await this.db.execute<{
|
||||
id: string;
|
||||
catalog_id: string;
|
||||
parent_id: string | null;
|
||||
name: string;
|
||||
img_url: string | null;
|
||||
has_subgroups: boolean;
|
||||
has_parts: boolean;
|
||||
}>(
|
||||
sql`SELECT id, catalog_id, parent_id, name, img_url, has_subgroups, has_parts FROM pc.groups WHERE catalog_id = ${catalogId} AND parent_id IS NULL ORDER BY sort_order, name`,
|
||||
);
|
||||
|
||||
const result: PcatGroupDto[] = rows.map((r) => ({
|
||||
id: r.id,
|
||||
catalogId: r.catalog_id,
|
||||
parentId: r.parent_id,
|
||||
name: r.name,
|
||||
imgUrl: r.img_url,
|
||||
hasSubgroups: r.has_subgroups ?? false,
|
||||
hasParts: r.has_parts ?? false,
|
||||
}));
|
||||
|
||||
await this.redis.setJson(cacheKey, result, CACHE_TTL.groups);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user