Files
sase.tr/scripts/migration-shared-vehicles.sql
Sase Dev 72c0de65de 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>
2026-03-02 00:31:15 +00:00

123 lines
4.0 KiB
PL/PgSQL

-- Migration: Shared Vehicle Config (car-config-centric architecture)
-- Converts per-user vehicle records to shared vehicle configs with junction table.
-- IMPORTANT: Take a DB backup before running. Execute inside a transaction.
BEGIN;
-- 1. Create user_vehicles junction table
CREATE TABLE user_vehicles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
vehicle_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. For each VIN, pick the canonical record (most recently updated)
-- and migrate all userId-vehicleId pairs into the junction table
INSERT INTO user_vehicles (user_id, vehicle_id, created_at, last_accessed_at)
SELECT
v.user_id,
canonical.id,
v.created_at,
v.updated_at
FROM vehicles v
JOIN (
SELECT DISTINCT ON (vin) id, vin
FROM vehicles
ORDER BY vin, updated_at DESC NULLS LAST
) canonical ON canonical.vin = v.vin;
-- 3. Delete categories on NON-canonical vehicles that already exist on the
-- canonical vehicle (same name+source), so the UPDATE in step 4 won't
-- hit the unique constraint.
WITH canonical AS (
SELECT DISTINCT ON (vin) id AS cid, vin
FROM vehicles ORDER BY vin, updated_at DESC NULLS LAST
)
DELETE FROM categories
WHERE id IN (
SELECT c_dup.id
FROM categories c_dup
JOIN vehicles v ON c_dup.vehicle_id = v.id
JOIN canonical can ON can.vin = v.vin
WHERE v.id != can.cid
AND EXISTS (
SELECT 1 FROM categories c_can
WHERE c_can.vehicle_id = can.cid
AND c_can.name = c_dup.name
AND c_can.source = c_dup.source
)
);
-- 4. Move remaining categories from non-canonical vehicles to canonical
WITH canonical AS (
SELECT DISTINCT ON (vin) id AS cid, vin
FROM vehicles ORDER BY vin, updated_at DESC NULLS LAST
)
UPDATE categories c
SET vehicle_id = can.cid
FROM vehicles v JOIN canonical can ON can.vin = v.vin
WHERE c.vehicle_id = v.id AND v.id != can.cid;
-- 5. Delete parts on non-canonical vehicles that would conflict after move
-- (parts don't have a unique constraint, but let's keep data clean by
-- removing duplicates — same oemCode+categoryId on canonical vehicle)
WITH canonical AS (
SELECT DISTINCT ON (vin) id AS cid, vin
FROM vehicles ORDER BY vin, updated_at DESC NULLS LAST
)
DELETE FROM parts
WHERE id IN (
SELECT p_dup.id
FROM parts p_dup
JOIN vehicles v ON p_dup.vehicle_id = v.id
JOIN canonical can ON can.vin = v.vin
WHERE v.id != can.cid
AND EXISTS (
SELECT 1 FROM parts p_can
WHERE p_can.vehicle_id = can.cid
AND p_can.category_id = p_dup.category_id
AND p_can.oem_code = p_dup.oem_code
)
);
-- 6. Move remaining parts from non-canonical vehicles to canonical
WITH canonical AS (
SELECT DISTINCT ON (vin) id AS cid, vin
FROM vehicles ORDER BY vin, updated_at DESC NULLS LAST
)
UPDATE parts p
SET vehicle_id = can.cid
FROM vehicles v JOIN canonical can ON can.vin = v.vin
WHERE p.vehicle_id = v.id AND v.id != can.cid;
-- 7. Delete non-canonical vehicle records (duplicates)
DELETE FROM vehicles WHERE id NOT IN (
SELECT DISTINCT ON (vin) id FROM vehicles
ORDER BY vin, updated_at DESC NULLS LAST
);
-- 8. Drop old user-specific indexes and column
DROP INDEX IF EXISTS vehicles_user_vin_idx;
DROP INDEX IF EXISTS vehicles_user_id_idx;
ALTER TABLE vehicles DROP COLUMN user_id;
-- 9. Add unique index on VIN (one record per VIN globally)
CREATE UNIQUE INDEX vehicles_vin_unique_idx ON vehicles (vin);
-- 10. Add FK and indexes on junction table
ALTER TABLE user_vehicles
ADD CONSTRAINT user_vehicles_vehicle_id_fkey
FOREIGN KEY (vehicle_id) REFERENCES vehicles(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX user_vehicles_user_vehicle_idx ON user_vehicles (user_id, vehicle_id);
CREATE INDEX user_vehicles_user_id_idx ON user_vehicles (user_id);
-- 11. Drop unused vehicle_categories table
DROP TABLE IF EXISTS vehicle_categories;
-- Also drop the old vin-only index if it exists (replaced by unique)
DROP INDEX IF EXISTS vehicles_vin_idx;
COMMIT;