docs: update INDEX.md + add catalog module, Ford/PSA legacy catalog, shared vehicles
- CatalogModule: VIN-less PL24 catalog browser (brands, models, categories, parts) - Supports P5 Modern (REST) and P4 Legacy (Ford, PSA) catalog architectures - Ford variant selector (model-year/engine/gearbox), PSA variant selector (body/engine/gearbox) - New API endpoints: ford-config, psa-bodies, psa-engines, psa-gearboxes, brands/:name/catalogs - Shared vehicles: vehicles table decoupled from users via userVehicles junction table - PL24 Ford Legacy service: comprehensive HTML-scraping for Ford/PSA/Hyundai/Kia/Nissan/Opel/Volvo - PL24 types and service updated for P4 Legacy brand support - Categories/parts service updated for dual FK (vehicleId + catalogVehicleId) pattern - Catalog browser frontend routes and components - docs/INDEX.md: updated with all new endpoints, components, hooks, routes (2026-03-02) - docs/pl24-catalog/: per-brand catalog exploration docs - scripts/migration-shared-vehicles.sql, pl24-catalog-explorer.js, posthog-dashboards.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -231,6 +231,36 @@ export const oemCodeCopies = pgTable(
|
||||
],
|
||||
);
|
||||
|
||||
// ─── Catalog Vehicles (VIN-less catalog browse — one record per PL24 vehicleId) ──
|
||||
export const catalogVehicles = pgTable(
|
||||
"catalog_vehicles",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
source: varchar("source", { length: 20 }).default("pl24").notNull(),
|
||||
serviceName: varchar("service_name", { length: 100 }).notNull(),
|
||||
brandName: varchar("brand_name", { length: 100 }).notNull(),
|
||||
brandId: uuid("brand_id").references(() => brands.id),
|
||||
model: varchar("model", { length: 255 }).notNull(),
|
||||
year: varchar("year", { length: 50 }),
|
||||
engine: varchar("engine", { length: 255 }),
|
||||
bodyType: varchar("body_type", { length: 100 }),
|
||||
transmission: varchar("transmission", { length: 100 }),
|
||||
market: varchar("market", { length: 100 }),
|
||||
serviceVehicleId: varchar("service_vehicle_id", { length: 255 }).notNull(),
|
||||
catalogPath: text("catalog_path"),
|
||||
architecture: varchar("architecture", { length: 30 }),
|
||||
metadata: jsonb("metadata"),
|
||||
categoriesFetched: boolean("categories_fetched").default(false).notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("catalog_vehicles_source_vid_idx").on(table.source, table.serviceVehicleId),
|
||||
index("catalog_vehicles_brand_name_idx").on(table.brandName),
|
||||
index("catalog_vehicles_service_name_idx").on(table.serviceName),
|
||||
],
|
||||
);
|
||||
|
||||
// ─── Vehicles (shared config — one record per VIN) ──
|
||||
export const vehicles = pgTable(
|
||||
"vehicles",
|
||||
@@ -280,13 +310,12 @@ export const categories = pgTable(
|
||||
"categories",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
vehicleId: uuid("vehicle_id")
|
||||
.notNull()
|
||||
.references(() => vehicles.id, { onDelete: "cascade" }),
|
||||
vehicleId: uuid("vehicle_id").references(() => vehicles.id, { onDelete: "cascade" }),
|
||||
catalogVehicleId: uuid("catalog_vehicle_id").references(() => catalogVehicles.id, { onDelete: "cascade" }),
|
||||
name: varchar("name", { length: 500 }).notNull(),
|
||||
nameOriginal: varchar("name_original", { length: 500 }),
|
||||
parentId: uuid("parent_id"),
|
||||
externalId: varchar("external_id", { length: 500 }),
|
||||
externalId: text("external_id"),
|
||||
linkPath: text("link_path"),
|
||||
linkWid: varchar("link_wid", { length: 100 }),
|
||||
unavailable: boolean("unavailable").default(false).notNull(),
|
||||
@@ -295,8 +324,9 @@ export const categories = pgTable(
|
||||
},
|
||||
(table) => [
|
||||
index("categories_vehicle_id_idx").on(table.vehicleId),
|
||||
index("categories_catalog_vehicle_id_idx").on(table.catalogVehicleId),
|
||||
index("categories_parent_id_idx").on(table.parentId),
|
||||
uniqueIndex("categories_vehicle_name_source_idx").on(table.vehicleId, table.name, table.source),
|
||||
uniqueIndex("categories_vehicle_name_source_idx").on(table.vehicleId, table.catalogVehicleId, table.name, table.source),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -305,9 +335,8 @@ export const parts = pgTable(
|
||||
"parts",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
vehicleId: uuid("vehicle_id")
|
||||
.notNull()
|
||||
.references(() => vehicles.id, { onDelete: "cascade" }),
|
||||
vehicleId: uuid("vehicle_id").references(() => vehicles.id, { onDelete: "cascade" }),
|
||||
catalogVehicleId: uuid("catalog_vehicle_id").references(() => catalogVehicles.id, { onDelete: "cascade" }),
|
||||
categoryId: uuid("category_id")
|
||||
.notNull()
|
||||
.references(() => categories.id, { onDelete: "cascade" }),
|
||||
@@ -327,6 +356,7 @@ export const parts = pgTable(
|
||||
},
|
||||
(table) => [
|
||||
index("parts_vehicle_id_idx").on(table.vehicleId),
|
||||
index("parts_catalog_vehicle_id_idx").on(table.catalogVehicleId),
|
||||
index("parts_category_id_idx").on(table.categoryId),
|
||||
index("parts_oem_code_idx").on(table.oemCode),
|
||||
],
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
parts,
|
||||
schemaPics,
|
||||
referrals,
|
||||
catalogVehicles,
|
||||
} from "./core";
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
@@ -59,6 +60,13 @@ export const userBrandsRelations = relations(userBrands, ({ one }) => ({
|
||||
|
||||
export const brandsRelations = relations(brands, ({ many }) => ({
|
||||
userBrands: many(userBrands),
|
||||
catalogVehicles: many(catalogVehicles),
|
||||
}));
|
||||
|
||||
export const catalogVehiclesRelations = relations(catalogVehicles, ({ one, many }) => ({
|
||||
brand: one(brands, { fields: [catalogVehicles.brandId], references: [brands.id] }),
|
||||
categories: many(categories),
|
||||
parts: many(parts),
|
||||
}));
|
||||
|
||||
export const paymentsRelations = relations(payments, ({ one }) => ({
|
||||
@@ -88,6 +96,7 @@ export const userVehiclesRelations = relations(userVehicles, ({ one }) => ({
|
||||
|
||||
export const categoriesRelations = relations(categories, ({ one, many }) => ({
|
||||
vehicle: one(vehicles, { fields: [categories.vehicleId], references: [vehicles.id] }),
|
||||
catalogVehicle: one(catalogVehicles, { fields: [categories.catalogVehicleId], references: [catalogVehicles.id] }),
|
||||
parent: one(categories, {
|
||||
fields: [categories.parentId],
|
||||
references: [categories.id],
|
||||
@@ -100,6 +109,7 @@ export const categoriesRelations = relations(categories, ({ one, many }) => ({
|
||||
|
||||
export const partsRelations = relations(parts, ({ one }) => ({
|
||||
vehicle: one(vehicles, { fields: [parts.vehicleId], references: [vehicles.id] }),
|
||||
catalogVehicle: one(catalogVehicles, { fields: [parts.catalogVehicleId], references: [catalogVehicles.id] }),
|
||||
category: one(categories, { fields: [parts.categoryId], references: [categories.id] }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -4,33 +4,43 @@ import postgres from "postgres";
|
||||
import { brands, plans, users, accounts } from "./schema/core";
|
||||
|
||||
const BRANDS_DATA = [
|
||||
{ name: "BMW", slug: "bmw" },
|
||||
{ name: "Mercedes-Benz", slug: "mercedes-benz" },
|
||||
// Mevcut markalar
|
||||
{ name: "Audi", slug: "audi" },
|
||||
{ name: "Volkswagen", slug: "volkswagen" },
|
||||
{ name: "Fiat", slug: "fiat" },
|
||||
{ name: "Renault", slug: "renault" },
|
||||
{ name: "Peugeot", slug: "peugeot" },
|
||||
{ name: "BMW", slug: "bmw" },
|
||||
{ name: "Citroen", slug: "citroen" },
|
||||
{ name: "Toyota", slug: "toyota" },
|
||||
{ name: "Dacia", slug: "dacia" },
|
||||
{ name: "Fiat", slug: "fiat" },
|
||||
{ name: "Ford", slug: "ford" },
|
||||
{ name: "Honda", slug: "honda" },
|
||||
{ name: "Hyundai", slug: "hyundai" },
|
||||
{ name: "Kia", slug: "kia" },
|
||||
{ name: "Ford", slug: "ford" },
|
||||
{ name: "Opel", slug: "opel" },
|
||||
{ name: "Skoda", slug: "skoda" },
|
||||
{ name: "Seat", slug: "seat" },
|
||||
{ name: "Volvo", slug: "volvo" },
|
||||
{ name: "Nissan", slug: "nissan" },
|
||||
{ name: "Mazda", slug: "mazda" },
|
||||
{ name: "Porsche", slug: "porsche" },
|
||||
{ name: "Land Rover", slug: "land-rover" },
|
||||
{ name: "Jaguar", slug: "jaguar" },
|
||||
{ name: "Kia", slug: "kia" },
|
||||
{ name: "Land Rover", slug: "land-rover" },
|
||||
{ name: "Mazda", slug: "mazda" },
|
||||
{ name: "Mercedes-Benz", slug: "mercedes-benz" },
|
||||
{ name: "Mini", slug: "mini" },
|
||||
{ name: "Dacia", slug: "dacia" },
|
||||
{ name: "Mitsubishi", slug: "mitsubishi" },
|
||||
{ name: "Nissan", slug: "nissan" },
|
||||
{ name: "Opel", slug: "opel" },
|
||||
{ name: "Peugeot", slug: "peugeot" },
|
||||
{ name: "Porsche", slug: "porsche" },
|
||||
{ name: "Renault", slug: "renault" },
|
||||
{ name: "Seat", slug: "seat" },
|
||||
{ name: "Skoda", slug: "skoda" },
|
||||
{ name: "Subaru", slug: "subaru" },
|
||||
{ name: "Suzuki", slug: "suzuki" },
|
||||
{ name: "Mitsubishi", slug: "mitsubishi" },
|
||||
{ name: "Toyota", slug: "toyota" },
|
||||
{ name: "Volkswagen", slug: "volkswagen" },
|
||||
{ name: "Volvo", slug: "volvo" },
|
||||
// PL24 kataloglarında olan, sase.tr'ye eklenen markalar
|
||||
{ name: "Alpine", slug: "alpine" },
|
||||
{ name: "Bentley", slug: "bentley" },
|
||||
{ name: "Cupra", slug: "cupra" },
|
||||
{ name: "Infiniti", slug: "infiniti" },
|
||||
{ name: "Lexus", slug: "lexus" },
|
||||
{ name: "MAN", slug: "man" },
|
||||
{ name: "Polestar", slug: "polestar" },
|
||||
{ name: "Smart", slug: "smart" },
|
||||
];
|
||||
|
||||
const PLANS_DATA = [
|
||||
|
||||
Reference in New Issue
Block a user