Merge pull request #10 from semihyesilyurt/dev
feat(FN-190): move drizzle-kit to production dependencies (+8 more)
This commit is contained in:
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
cd /home/${{ secrets.SSH_USER }}/ss
|
||||
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Running database migrations..."
|
||||
cd apps/api && pnpm db:push && cd ../..
|
||||
cd apps/api && pnpm db:migrate && cd ../..
|
||||
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - Reloading PM2 processes..."
|
||||
pm2 reload ecosystem.config.js
|
||||
|
||||
@@ -59,6 +59,10 @@ COPY --from=build /app/packages/ui/package.json ./packages/ui/
|
||||
# NestJS CLI config (needed for serve-static)
|
||||
COPY --from=build /app/apps/api/nest-cli.json ./apps/api/
|
||||
|
||||
# Drizzle migrations + startup wrapper
|
||||
COPY --from=build /app/apps/api/drizzle ./apps/api/drizzle
|
||||
COPY --from=build /app/apps/api/start.sh ./apps/api/start.sh
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@10.29.3 --activate \
|
||||
&& pnpm install --frozen-lockfile --prod \
|
||||
&& pnpm store prune
|
||||
@@ -66,4 +70,4 @@ RUN corepack enable && corepack prepare pnpm@10.29.3 --activate \
|
||||
ENV NODE_ENV=production
|
||||
EXPOSE 4000
|
||||
|
||||
CMD ["node", "--enable-source-maps", "apps/api/dist/main.js"]
|
||||
CMD ["./apps/api/start.sh"]
|
||||
|
||||
658
apps/api/drizzle/0000_brief_guardian.sql
Normal file
658
apps/api/drizzle/0000_brief_guardian.sql
Normal file
@@ -0,0 +1,658 @@
|
||||
CREATE TABLE "accounts" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"account_id" text NOT NULL,
|
||||
"provider_id" varchar(50) NOT NULL,
|
||||
"access_token" text,
|
||||
"refresh_token" text,
|
||||
"access_token_expires_at" timestamp with time zone,
|
||||
"refresh_token_expires_at" timestamp with time zone,
|
||||
"scope" text,
|
||||
"id_token" text,
|
||||
"password" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "brands" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"slug" varchar(100) NOT NULL,
|
||||
"logo_url" text,
|
||||
"is_active" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "catalog_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"source" varchar(20) DEFAULT 'pl24' NOT NULL,
|
||||
"service_name" varchar(100) NOT NULL,
|
||||
"brand_name" varchar(100) NOT NULL,
|
||||
"brand_id" uuid,
|
||||
"model" varchar(255) NOT NULL,
|
||||
"year" varchar(50),
|
||||
"engine" varchar(255),
|
||||
"body_type" varchar(100),
|
||||
"transmission" varchar(100),
|
||||
"market" varchar(100),
|
||||
"service_vehicle_id" varchar(255) NOT NULL,
|
||||
"catalog_path" text,
|
||||
"architecture" varchar(30),
|
||||
"metadata" jsonb,
|
||||
"categories_fetched" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "categories" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vehicle_id" uuid,
|
||||
"catalog_vehicle_id" uuid,
|
||||
"name" varchar(500) NOT NULL,
|
||||
"name_original" varchar(500),
|
||||
"parent_id" uuid,
|
||||
"external_id" text,
|
||||
"link_path" text,
|
||||
"link_wid" varchar(100),
|
||||
"unavailable" boolean DEFAULT false NOT NULL,
|
||||
"source" varchar(20) DEFAULT 'pl24' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "changelog_entries" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"stage" varchar(10) DEFAULT 'prod' NOT NULL,
|
||||
"title" varchar(255) NOT NULL,
|
||||
"description" text NOT NULL,
|
||||
"published_at" timestamp with time zone NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_category_translations" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"original_name" varchar(500) NOT NULL,
|
||||
"translated_name" varchar(500) NOT NULL,
|
||||
"is_manual" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "oem_code_copies" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
"part_id" uuid,
|
||||
"vehicle_id" uuid,
|
||||
"category_id" uuid,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vehicle_id" uuid,
|
||||
"catalog_vehicle_id" uuid,
|
||||
"category_id" uuid NOT NULL,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
"name" varchar(500) NOT NULL,
|
||||
"name_original" varchar(500),
|
||||
"description" text,
|
||||
"quantity" integer,
|
||||
"position" varchar(100),
|
||||
"hotspot_index" integer,
|
||||
"unavailable" boolean DEFAULT false NOT NULL,
|
||||
"remark" text,
|
||||
"model_codes" varchar(500),
|
||||
"presel" boolean DEFAULT false NOT NULL,
|
||||
"price" numeric(10, 2),
|
||||
"currency" varchar(3),
|
||||
"source" varchar(20) DEFAULT 'pl24' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "password_reset_tokens" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"used_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "payments" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"subscription_id" uuid NOT NULL,
|
||||
"amount" integer NOT NULL,
|
||||
"currency" varchar(3) DEFAULT 'TRY' NOT NULL,
|
||||
"method" varchar(20) NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'pending' NOT NULL,
|
||||
"iyzico_payment_id" text,
|
||||
"eft_receipt_url" text,
|
||||
"admin_note" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "plans" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"brand_count" integer NOT NULL,
|
||||
"price_monthly" integer NOT NULL,
|
||||
"price_yearly" integer NOT NULL,
|
||||
"is_active" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "query_logs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"brand_id" uuid,
|
||||
"source" varchar(20),
|
||||
"success" boolean DEFAULT true NOT NULL,
|
||||
"error_message" text,
|
||||
"response_time_ms" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "referrals" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"referrer_id" uuid NOT NULL,
|
||||
"referred_id" uuid NOT NULL,
|
||||
"reward_applied" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "schema_pics" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"category_id" uuid NOT NULL,
|
||||
"image_url" text NOT NULL,
|
||||
"hotspots" jsonb DEFAULT '[]' NOT NULL,
|
||||
"source" varchar(20) DEFAULT 'pl24' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "sessions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"ip_address" varchar(45),
|
||||
"user_agent" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_brands" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"subscription_id" uuid NOT NULL,
|
||||
"brand_id" uuid NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_subscriptions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"plan_id" uuid NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'pending' NOT NULL,
|
||||
"billing_period" varchar(10) DEFAULT 'monthly' NOT NULL,
|
||||
"start_date" timestamp with time zone,
|
||||
"end_date" timestamp with time zone,
|
||||
"cancelled_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"vehicle_id" uuid NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"last_accessed_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"email_verified" boolean DEFAULT false NOT NULL,
|
||||
"image" text,
|
||||
"role" varchar(20) DEFAULT 'user' NOT NULL,
|
||||
"referral_code" varchar(20),
|
||||
"referred_by" uuid,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"brand_id" uuid,
|
||||
"brand_name" varchar(100),
|
||||
"model" varchar(255),
|
||||
"year" integer,
|
||||
"engine" varchar(255),
|
||||
"transmission" varchar(100),
|
||||
"body_type" varchar(100),
|
||||
"market" varchar(100),
|
||||
"raw_data" jsonb,
|
||||
"source" varchar(20) DEFAULT 'pl24' NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "verifications" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"expires_at" timestamp with time zone NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_catalogs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" varchar(100) NOT NULL,
|
||||
"code" varchar(50),
|
||||
"brand_name" varchar(100) NOT NULL,
|
||||
"description" text,
|
||||
"source_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_part_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_catalog_id" uuid,
|
||||
"group_id" varchar(100) NOT NULL,
|
||||
"name" varchar(500) NOT NULL,
|
||||
"name_original" varchar(500),
|
||||
"parent_group_id" varchar(100),
|
||||
"sort_order" integer,
|
||||
"has_parts" boolean DEFAULT false,
|
||||
"has_children" boolean DEFAULT false,
|
||||
"source_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_part_images" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_part_id" uuid,
|
||||
"image_url" text NOT NULL,
|
||||
"original_url" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_part_numbers" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_part_id" uuid,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
"is_main" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_catalog_id" uuid,
|
||||
"group_id" uuid,
|
||||
"part_id" varchar(100),
|
||||
"part_number" varchar(100),
|
||||
"name" varchar(500) NOT NULL,
|
||||
"name_original" varchar(500),
|
||||
"description" text,
|
||||
"oem_number" varchar(100),
|
||||
"hotspot_index" integer,
|
||||
"raw_data" jsonb,
|
||||
"source_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_schema_pics" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"group_id" uuid,
|
||||
"image_url" text,
|
||||
"original_url" text,
|
||||
"local_path" varchar(500),
|
||||
"hotspots" jsonb DEFAULT '[]' NOT NULL,
|
||||
"width" integer,
|
||||
"height" integer,
|
||||
"sort_order" integer DEFAULT 0,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_scrape_sessions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'pending' NOT NULL,
|
||||
"job_id" varchar(100),
|
||||
"result" jsonb,
|
||||
"error_message" text,
|
||||
"started_at" timestamp with time zone,
|
||||
"completed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicle_group_links" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_vehicle_id" uuid NOT NULL,
|
||||
"emex_group_id" uuid NOT NULL,
|
||||
"ssd" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicle_part_links" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_vehicle_id" uuid NOT NULL,
|
||||
"emex_part_id" uuid NOT NULL,
|
||||
"emex_group_id" uuid,
|
||||
"quantity" integer,
|
||||
"position" varchar(100),
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicle_vins" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"emex_vehicle_id" uuid,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "emex_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" uuid,
|
||||
"vehicle_id" varchar(100) NOT NULL,
|
||||
"name" varchar(500),
|
||||
"model_code" varchar(100),
|
||||
"engine" varchar(255),
|
||||
"engine_code" varchar(100),
|
||||
"body_type" varchar(100),
|
||||
"transmission" varchar(100),
|
||||
"drive_type" varchar(100),
|
||||
"fuel_type" varchar(100),
|
||||
"year_from" integer,
|
||||
"year_to" integer,
|
||||
"ssd" text,
|
||||
"options_raw" text,
|
||||
"raw_data" jsonb,
|
||||
"source_id" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_part_groups" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"catalog_id" text NOT NULL,
|
||||
"parent_id" text,
|
||||
"name" text NOT NULL,
|
||||
"img_url" text,
|
||||
"has_subgroups" boolean DEFAULT false NOT NULL,
|
||||
"has_parts" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_parts" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"notice" text,
|
||||
"description" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_schema_pics" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"group_id" text NOT NULL,
|
||||
"car_id" text NOT NULL,
|
||||
"img_url" text NOT NULL,
|
||||
"img_description" text,
|
||||
"hotspots" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pcat_vehicles" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"catalog_id" text NOT NULL,
|
||||
"vin" varchar(17),
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"parameters" jsonb,
|
||||
"raw_data" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_catalogs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" varchar(100) NOT NULL,
|
||||
"brand_name" varchar(100) NOT NULL,
|
||||
"description" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_part_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"group_id" varchar(100) NOT NULL,
|
||||
"name" varchar(500) NOT NULL,
|
||||
"parent_group_id" varchar(100),
|
||||
"sort_order" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_part_images" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_part_id" uuid,
|
||||
"image_url" text NOT NULL,
|
||||
"original_url" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_part_numbers" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_part_id" uuid,
|
||||
"oem_code" varchar(100) NOT NULL,
|
||||
"is_main" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"group_id" uuid,
|
||||
"part_id" varchar(100),
|
||||
"name" varchar(500) NOT NULL,
|
||||
"description" text,
|
||||
"quantity" integer,
|
||||
"position" varchar(100),
|
||||
"hotspot_index" integer,
|
||||
"raw_data" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_schema_pics" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"group_id" uuid,
|
||||
"image_url" text NOT NULL,
|
||||
"original_url" text,
|
||||
"hotspots" jsonb DEFAULT '[]' NOT NULL,
|
||||
"width" integer,
|
||||
"height" integer,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicle_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" uuid,
|
||||
"group_id" varchar(100) NOT NULL,
|
||||
"name" varchar(500) NOT NULL,
|
||||
"parent_group_id" varchar(100),
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicle_parts" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"pl24_part_id" uuid,
|
||||
"fitment_info" text,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicle_vins" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pl24_vehicle_id" uuid,
|
||||
"vin" varchar(17) NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "pl24_vehicles" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"catalog_id" uuid,
|
||||
"vehicle_id" varchar(100) NOT NULL,
|
||||
"name" varchar(500),
|
||||
"model_code" varchar(100),
|
||||
"engine" varchar(255),
|
||||
"transmission" varchar(100),
|
||||
"body_type" varchar(100),
|
||||
"market" varchar(100),
|
||||
"year_from" integer,
|
||||
"year_to" integer,
|
||||
"raw_data" jsonb,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "catalog_vehicles" ADD CONSTRAINT "catalog_vehicles_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "categories" ADD CONSTRAINT "categories_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "categories" ADD CONSTRAINT "categories_catalog_vehicle_id_catalog_vehicles_id_fk" FOREIGN KEY ("catalog_vehicle_id") REFERENCES "public"."catalog_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "oem_code_copies" ADD CONSTRAINT "oem_code_copies_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "parts" ADD CONSTRAINT "parts_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "parts" ADD CONSTRAINT "parts_catalog_vehicle_id_catalog_vehicles_id_fk" FOREIGN KEY ("catalog_vehicle_id") REFERENCES "public"."catalog_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "parts" ADD CONSTRAINT "parts_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "payments" ADD CONSTRAINT "payments_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "payments" ADD CONSTRAINT "payments_subscription_id_user_subscriptions_id_fk" FOREIGN KEY ("subscription_id") REFERENCES "public"."user_subscriptions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "query_logs" ADD CONSTRAINT "query_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "query_logs" ADD CONSTRAINT "query_logs_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referrer_id_users_id_fk" FOREIGN KEY ("referrer_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referred_id_users_id_fk" FOREIGN KEY ("referred_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "schema_pics" ADD CONSTRAINT "schema_pics_category_id_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_subscription_id_user_subscriptions_id_fk" FOREIGN KEY ("subscription_id") REFERENCES "public"."user_subscriptions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_brands" ADD CONSTRAINT "user_brands_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_subscriptions" ADD CONSTRAINT "user_subscriptions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_subscriptions" ADD CONSTRAINT "user_subscriptions_plan_id_plans_id_fk" FOREIGN KEY ("plan_id") REFERENCES "public"."plans"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_vehicles" ADD CONSTRAINT "user_vehicles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_vehicles" ADD CONSTRAINT "user_vehicles_vehicle_id_vehicles_id_fk" FOREIGN KEY ("vehicle_id") REFERENCES "public"."vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "vehicles" ADD CONSTRAINT "vehicles_brand_id_brands_id_fk" FOREIGN KEY ("brand_id") REFERENCES "public"."brands"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_part_groups" ADD CONSTRAINT "emex_part_groups_emex_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("emex_catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_part_images" ADD CONSTRAINT "emex_part_images_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_part_numbers" ADD CONSTRAINT "emex_part_numbers_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_parts" ADD CONSTRAINT "emex_parts_emex_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("emex_catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_parts" ADD CONSTRAINT "emex_parts_group_id_emex_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_schema_pics" ADD CONSTRAINT "emex_schema_pics_group_id_emex_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_group_links" ADD CONSTRAINT "emex_vehicle_group_links_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_group_links" ADD CONSTRAINT "emex_vehicle_group_links_emex_group_id_emex_part_groups_id_fk" FOREIGN KEY ("emex_group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_part_id_emex_parts_id_fk" FOREIGN KEY ("emex_part_id") REFERENCES "public"."emex_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_part_links" ADD CONSTRAINT "emex_vehicle_part_links_emex_group_id_emex_part_groups_id_fk" FOREIGN KEY ("emex_group_id") REFERENCES "public"."emex_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicle_vins" ADD CONSTRAINT "emex_vehicle_vins_emex_vehicle_id_emex_vehicles_id_fk" FOREIGN KEY ("emex_vehicle_id") REFERENCES "public"."emex_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "emex_vehicles" ADD CONSTRAINT "emex_vehicles_catalog_id_emex_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."emex_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_part_groups" ADD CONSTRAINT "pl24_part_groups_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_part_images" ADD CONSTRAINT "pl24_part_images_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_part_numbers" ADD CONSTRAINT "pl24_part_numbers_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_parts" ADD CONSTRAINT "pl24_parts_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_parts" ADD CONSTRAINT "pl24_parts_group_id_pl24_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."pl24_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_schema_pics" ADD CONSTRAINT "pl24_schema_pics_group_id_pl24_part_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "public"."pl24_part_groups"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_groups" ADD CONSTRAINT "pl24_vehicle_groups_catalog_id_pl24_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."pl24_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_parts" ADD CONSTRAINT "pl24_vehicle_parts_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_parts" ADD CONSTRAINT "pl24_vehicle_parts_pl24_part_id_pl24_parts_id_fk" FOREIGN KEY ("pl24_part_id") REFERENCES "public"."pl24_parts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicle_vins" ADD CONSTRAINT "pl24_vehicle_vins_pl24_vehicle_id_pl24_vehicles_id_fk" FOREIGN KEY ("pl24_vehicle_id") REFERENCES "public"."pl24_vehicles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "pl24_vehicles" ADD CONSTRAINT "pl24_vehicles_catalog_id_pl24_catalogs_id_fk" FOREIGN KEY ("catalog_id") REFERENCES "public"."pl24_catalogs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "accounts_user_id_idx" ON "accounts" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "brands_slug_idx" ON "brands" USING btree ("slug");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "catalog_vehicles_source_svc_vid_idx" ON "catalog_vehicles" USING btree ("source","service_name","service_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "catalog_vehicles_brand_name_idx" ON "catalog_vehicles" USING btree ("brand_name");--> statement-breakpoint
|
||||
CREATE INDEX "catalog_vehicles_service_name_idx" ON "catalog_vehicles" USING btree ("service_name");--> statement-breakpoint
|
||||
CREATE INDEX "categories_vehicle_id_idx" ON "categories" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "categories_catalog_vehicle_id_idx" ON "categories" USING btree ("catalog_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "categories_parent_id_idx" ON "categories" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "categories_vehicle_name_source_idx" ON "categories" USING btree ("vehicle_id","catalog_vehicle_id","name","source");--> statement-breakpoint
|
||||
CREATE INDEX "changelog_entries_published_at_idx" ON "changelog_entries" USING btree ("published_at");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_translations_original_name_idx" ON "emex_category_translations" USING btree ("original_name");--> statement-breakpoint
|
||||
CREATE INDEX "oem_code_copies_user_id_created_at_idx" ON "oem_code_copies" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "oem_code_copies_oem_code_idx" ON "oem_code_copies" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX "oem_code_copies_created_at_idx" ON "oem_code_copies" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "parts_vehicle_id_idx" ON "parts" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "parts_catalog_vehicle_id_idx" ON "parts" USING btree ("catalog_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "parts_category_id_idx" ON "parts" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE INDEX "parts_oem_code_idx" ON "parts" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "password_reset_tokens_token_idx" ON "password_reset_tokens" USING btree ("token");--> statement-breakpoint
|
||||
CREATE INDEX "payments_user_id_idx" ON "payments" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "payments_status_idx" ON "payments" USING btree ("status");--> statement-breakpoint
|
||||
CREATE INDEX "query_logs_user_id_created_at_idx" ON "query_logs" USING btree ("user_id","created_at");--> statement-breakpoint
|
||||
CREATE INDEX "query_logs_vin_idx" ON "query_logs" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "referrals_referrer_id_idx" ON "referrals" USING btree ("referrer_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "referrals_referred_id_idx" ON "referrals" USING btree ("referred_id");--> statement-breakpoint
|
||||
CREATE INDEX "schema_pics_category_id_idx" ON "schema_pics" USING btree ("category_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "sessions_token_idx" ON "sessions" USING btree ("token");--> statement-breakpoint
|
||||
CREATE INDEX "user_brands_user_id_idx" ON "user_brands" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_brands_unique_idx" ON "user_brands" USING btree ("user_id","subscription_id","brand_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_subscriptions_user_id_idx" ON "user_subscriptions" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_subscriptions_status_idx" ON "user_subscriptions" USING btree ("status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_vehicles_user_vehicle_idx" ON "user_vehicles" USING btree ("user_id","vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_vehicles_user_id_idx" ON "user_vehicles" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "users_referral_code_idx" ON "users" USING btree ("referral_code");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "vehicles_vin_unique_idx" ON "vehicles" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "verifications_identifier_idx" ON "verifications" USING btree ("identifier");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_catalogs_catalog_id_idx" ON "emex_catalogs" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_catalogs_code_idx" ON "emex_catalogs" USING btree ("code");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_groups_catalog_id_idx" ON "emex_part_groups" USING btree ("emex_catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_groups_group_id_idx" ON "emex_part_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_part_groups_catalog_group_idx" ON "emex_part_groups" USING btree ("emex_catalog_id","group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_images_part_id_idx" ON "emex_part_images" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_numbers_part_id_idx" ON "emex_part_numbers" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_part_numbers_oem_code_idx" ON "emex_part_numbers" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_catalog_id_idx" ON "emex_parts" USING btree ("emex_catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_group_id_idx" ON "emex_parts" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_part_number_idx" ON "emex_parts" USING btree ("part_number");--> statement-breakpoint
|
||||
CREATE INDEX "emex_parts_oem_number_idx" ON "emex_parts" USING btree ("oem_number");--> statement-breakpoint
|
||||
CREATE INDEX "emex_schema_pics_group_id_idx" ON "emex_schema_pics" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_schema_pics_group_path_idx" ON "emex_schema_pics" USING btree ("group_id","local_path");--> statement-breakpoint
|
||||
CREATE INDEX "emex_scrape_sessions_vin_idx" ON "emex_scrape_sessions" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "emex_scrape_sessions_status_idx" ON "emex_scrape_sessions" USING btree ("status");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vgl_vehicle_group_idx" ON "emex_vehicle_group_links" USING btree ("emex_vehicle_id","emex_group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vgl_group_idx" ON "emex_vehicle_group_links" USING btree ("emex_group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vpl_vehicle_part_group_idx" ON "emex_vehicle_part_links" USING btree ("emex_vehicle_id","emex_part_id","emex_group_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vpl_part_idx" ON "emex_vehicle_part_links" USING btree ("emex_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vpl_group_idx" ON "emex_vehicle_part_links" USING btree ("emex_group_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vehicle_vins_vin_idx" ON "emex_vehicle_vins" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicle_vins_vehicle_id_idx" ON "emex_vehicle_vins" USING btree ("emex_vehicle_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "emex_vehicles_vehicle_id_idx" ON "emex_vehicles" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicles_catalog_id_idx" ON "emex_vehicles" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicles_name_idx" ON "emex_vehicles" USING btree ("name");--> statement-breakpoint
|
||||
CREATE INDEX "emex_vehicles_source_id_idx" ON "emex_vehicles" USING btree ("source_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_part_groups_catalog_id_idx" ON "pcat_part_groups" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_part_groups_parent_id_idx" ON "pcat_part_groups" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_parts_name_idx" ON "pcat_parts" USING btree ("name");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_schema_pics_group_car_idx" ON "pcat_schema_pics" USING btree ("group_id","car_id");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_vehicles_vin_idx" ON "pcat_vehicles" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "pcat_vehicles_catalog_id_idx" ON "pcat_vehicles" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "pl24_catalogs_catalog_id_idx" ON "pl24_catalogs" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_groups_vehicle_id_idx" ON "pl24_part_groups" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_groups_group_id_idx" ON "pl24_part_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_images_part_id_idx" ON "pl24_part_images" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_numbers_part_id_idx" ON "pl24_part_numbers" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_part_numbers_oem_code_idx" ON "pl24_part_numbers" USING btree ("oem_code");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_parts_vehicle_id_idx" ON "pl24_parts" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_parts_group_id_idx" ON "pl24_parts" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_schema_pics_group_id_idx" ON "pl24_schema_pics" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_groups_catalog_id_idx" ON "pl24_vehicle_groups" USING btree ("catalog_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_groups_group_id_idx" ON "pl24_vehicle_groups" USING btree ("group_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_parts_vehicle_id_idx" ON "pl24_vehicle_parts" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_parts_part_id_idx" ON "pl24_vehicle_parts" USING btree ("pl24_part_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_vins_vin_idx" ON "pl24_vehicle_vins" USING btree ("vin");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicle_vins_vehicle_id_idx" ON "pl24_vehicle_vins" USING btree ("pl24_vehicle_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "pl24_vehicles_vehicle_id_idx" ON "pl24_vehicles" USING btree ("vehicle_id");--> statement-breakpoint
|
||||
CREATE INDEX "pl24_vehicles_catalog_id_idx" ON "pl24_vehicles" USING btree ("catalog_id");
|
||||
5167
apps/api/drizzle/meta/0000_snapshot.json
Normal file
5167
apps/api/drizzle/meta/0000_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
13
apps/api/drizzle/meta/_journal.json
Normal file
13
apps/api/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1778535325959,
|
||||
"tag": "0000_brief_guardian",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:seed": "tsx src/database/seed.ts",
|
||||
"db:migrate": "tsx src/database/migrate.ts",
|
||||
"db:migrate:dist": "node dist/database/migrate.js",
|
||||
"worker": "node dist/worker.js",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
@@ -52,6 +54,7 @@
|
||||
"bullmq": "^5.30.0",
|
||||
"bullmq-otel": "^1.2.0",
|
||||
"dotenv": "^16.4.0",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.41.0",
|
||||
"helmet": "^8.1.0",
|
||||
"ioredis": "^5.4.0",
|
||||
@@ -70,7 +73,6 @@
|
||||
"@types/multer": "^1.4.0",
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"playwright": "^1.50.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.7.0",
|
||||
|
||||
179
apps/api/src/database/__tests__/migrate.spec.ts
Normal file
179
apps/api/src/database/__tests__/migrate.spec.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
/**
|
||||
* Unit tests for the migration runner's bootstrap logic.
|
||||
*
|
||||
* We test bootstrapExistingDb() directly with a mock postgres.Sql client
|
||||
* and mock the filesystem via vi.mock("node:fs").
|
||||
*/
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
return {
|
||||
...actual,
|
||||
readFileSync: vi.fn((path: string, _encoding: string) => {
|
||||
// Default: no file — will cause errors unless overridden in test setup
|
||||
throw new Error(`ENOENT: mock file not set up: ${path}`);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import { bootstrapExistingDb } from "../../database/migrate";
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
interface MockSqlClient {
|
||||
(queryParts: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
|
||||
unsafe: ReturnType<typeof vi.fn>;
|
||||
end: ReturnType<typeof vi.fn>;
|
||||
__inserts: string[];
|
||||
__unsafeCalls: string[];
|
||||
}
|
||||
|
||||
function createMockSqlClient(): MockSqlClient {
|
||||
let queryHandler: (template: string) => unknown[] = () => [];
|
||||
|
||||
const sql = vi.fn(async (queryParts: TemplateStringsArray, ..._values: unknown[]) => {
|
||||
const queryText = queryParts.join(" ").trim();
|
||||
// Capture INSERT queries directly
|
||||
if (queryText.includes("INSERT INTO")) {
|
||||
(sql as any).__inserts.push(queryText);
|
||||
return [];
|
||||
}
|
||||
return queryHandler(queryText);
|
||||
}) as unknown as MockSqlClient;
|
||||
|
||||
(sql as any).unsafe = vi.fn(async (q: string) => {
|
||||
((sql as any).__unsafeCalls as string[]).push(q);
|
||||
});
|
||||
(sql as any).end = vi.fn().mockResolvedValue(undefined);
|
||||
(sql as any).__inserts = [];
|
||||
(sql as any).__unsafeCalls = [];
|
||||
|
||||
// Allow tests to set queryHandler
|
||||
(sql as any)._setQueryHandler = (handler: typeof queryHandler) => {
|
||||
queryHandler = handler;
|
||||
};
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
// ── Tests ──
|
||||
|
||||
describe("bootstrapExistingDb", () => {
|
||||
let sqlClient: MockSqlClient;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
sqlClient = createMockSqlClient();
|
||||
});
|
||||
|
||||
describe("when __drizzle_migrations already exists", () => {
|
||||
it("should skip bootstrap (no CREATE, no INSERT)", async () => {
|
||||
(sqlClient as any)._setQueryHandler((queryText: string) => {
|
||||
if (
|
||||
queryText.includes("information_schema") &&
|
||||
queryText.includes("__drizzle_migrations")
|
||||
) {
|
||||
return [{ table_name: "__drizzle_migrations" }];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
|
||||
// Should NOT create schema or table
|
||||
expect((sqlClient as any).__unsafeCalls).toEqual([]);
|
||||
|
||||
// Should NOT insert any migration records
|
||||
expect((sqlClient as any).__inserts).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when __drizzle_migrations is missing but users exists (existing DB)", () => {
|
||||
it("should create schema, create table, mark baseline entries as applied", async () => {
|
||||
(sqlClient as any)._setQueryHandler((queryText: string) => {
|
||||
if (
|
||||
queryText.includes("information_schema") &&
|
||||
queryText.includes("__drizzle_migrations")
|
||||
) {
|
||||
return []; // does not exist
|
||||
}
|
||||
if (queryText.includes("information_schema") && queryText.includes("'users'")) {
|
||||
return [{ table_name: "users" }]; // users exist
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Set up the filesystem mocks
|
||||
const mockRFS = readFileSync as ReturnType<typeof vi.fn>;
|
||||
mockRFS.mockImplementation((path: string, _encoding: string) => {
|
||||
if (path.includes("_journal.json")) {
|
||||
return JSON.stringify({
|
||||
version: "7",
|
||||
dialect: "postgresql",
|
||||
entries: [
|
||||
{
|
||||
idx: 0,
|
||||
version: "7",
|
||||
when: 1778535325959,
|
||||
tag: "0000_brief_guardian",
|
||||
breakpoints: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (path.includes("0000_brief_guardian.sql")) {
|
||||
return "CREATE TABLE foo";
|
||||
}
|
||||
throw new Error(`Unexpected readFileSync: ${path}`);
|
||||
});
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
|
||||
// Verify CREATE SCHEMA was called
|
||||
expect((sqlClient as any).__unsafeCalls).toContain('CREATE SCHEMA IF NOT EXISTS "drizzle"');
|
||||
|
||||
// Verify CREATE TABLE was called
|
||||
const createTableCall = (sqlClient as any).__unsafeCalls.find((c: string) =>
|
||||
c.includes("CREATE TABLE IF NOT EXISTS"),
|
||||
);
|
||||
expect(createTableCall).toBeDefined();
|
||||
expect(createTableCall).toContain("__drizzle_migrations");
|
||||
|
||||
// Verify INSERT was called with a hash
|
||||
expect((sqlClient as any).__inserts.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when both __drizzle_migrations and users are missing (fresh DB)", () => {
|
||||
it("should skip bootstrap entirely", async () => {
|
||||
(sqlClient as any)._setQueryHandler((_queryText: string) => {
|
||||
return []; // nothing exists
|
||||
});
|
||||
|
||||
await bootstrapExistingDb(sqlClient as any);
|
||||
|
||||
// No schema creation
|
||||
expect((sqlClient as any).__unsafeCalls).toEqual([]);
|
||||
// No inserts
|
||||
expect((sqlClient as any).__inserts).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("hash computation", () => {
|
||||
it("should produce the expected SHA256 hash for a known input", () => {
|
||||
const input = "SELECT 1";
|
||||
const hash = createHash("sha256").update(input).digest("hex");
|
||||
expect(hash).toBe("e004ebd5b5532a4b85984a62f8ad48a81aa3460c1ca07701f386135d72cdecf5");
|
||||
});
|
||||
|
||||
it("should produce a different hash for different input", () => {
|
||||
const hashA = createHash("sha256").update("SELECT 1").digest("hex");
|
||||
const hashB = createHash("sha256").update("SELECT 2").digest("hex");
|
||||
expect(hashA).not.toBe(hashB);
|
||||
});
|
||||
});
|
||||
160
apps/api/src/database/migrate.ts
Normal file
160
apps/api/src/database/migrate.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||
import postgres from "postgres";
|
||||
|
||||
/**
|
||||
* Path to the drizzle migrations folder.
|
||||
*
|
||||
* In development (tsx), __dirname = <repo>/apps/api/src/database,
|
||||
* and the drizzle folder is at <repo>/apps/api/drizzle.
|
||||
*
|
||||
* In production (compiled), __dirname = <app>/apps/api/dist/database,
|
||||
* and the drizzle folder is COPIED to <app>/apps/api/drizzle.
|
||||
*
|
||||
* Both resolve correctly with: join(__dirname, "../../drizzle")
|
||||
*/
|
||||
const migrationsFolder = process.env.MIGRATIONS_FOLDER || join(__dirname, "../../drizzle");
|
||||
|
||||
interface JournalEntry {
|
||||
idx: number;
|
||||
version: string;
|
||||
when: number;
|
||||
tag: string;
|
||||
breakpoints: boolean;
|
||||
}
|
||||
|
||||
interface Journal {
|
||||
version: string;
|
||||
dialect: string;
|
||||
entries: JournalEntry[];
|
||||
}
|
||||
|
||||
export async function bootstrapExistingDb(sqlClient: postgres.Sql): Promise<void> {
|
||||
const tablesResult = await sqlClient`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'drizzle'
|
||||
AND table_name = '__drizzle_migrations'
|
||||
`;
|
||||
|
||||
if (tablesResult.length > 0) {
|
||||
// __drizzle_migrations already exists — normal migration run
|
||||
console.log("[migrate] Migration tracking table exists — skipping bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if users table exists (canary for existing DB)
|
||||
const usersResult = await sqlClient`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'users'
|
||||
`;
|
||||
|
||||
if (usersResult.length === 0) {
|
||||
// Fresh DB — migrate() will handle everything
|
||||
console.log("[migrate] Fresh database detected — skipping bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
// Existing DB with no migration tracking — bootstrap it
|
||||
console.log("[migrate] Bootstrapping existing database...");
|
||||
|
||||
// Create the drizzle schema if it doesn't exist
|
||||
await sqlClient.unsafe(`CREATE SCHEMA IF NOT EXISTS "drizzle"`);
|
||||
|
||||
// Create __drizzle_migrations table matching Drizzle's expected schema
|
||||
await sqlClient.unsafe(`
|
||||
CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" (
|
||||
id SERIAL PRIMARY KEY,
|
||||
hash text NOT NULL,
|
||||
created_at bigint
|
||||
)
|
||||
`);
|
||||
|
||||
// Read journal and mark baseline migrations as applied
|
||||
const journalPath = join(migrationsFolder, "meta", "_journal.json");
|
||||
let journal: Journal;
|
||||
|
||||
try {
|
||||
journal = JSON.parse(readFileSync(journalPath, "utf-8")) as Journal;
|
||||
} catch (err) {
|
||||
console.error("[migrate] Failed to read _journal.json:", (err as Error).message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!journal.entries || journal.entries.length === 0) {
|
||||
console.warn("[migrate] _journal.json has no entries — no migrations to bootstrap");
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
for (const entry of journal.entries) {
|
||||
const sqlPath = join(migrationsFolder, `${entry.tag}.sql`);
|
||||
let sqlContent: string;
|
||||
|
||||
try {
|
||||
sqlContent = readFileSync(sqlPath, "utf-8");
|
||||
} catch (err) {
|
||||
console.error(`[migrate] Failed to read migration ${entry.tag}.sql:`, (err as Error).message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const hash = createHash("sha256").update(sqlContent).digest("hex");
|
||||
await sqlClient`
|
||||
INSERT INTO "drizzle"."__drizzle_migrations" (hash, created_at)
|
||||
VALUES (${hash}, ${now})
|
||||
`;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[migrate] Bootstrapped existing DB: marked ${journal.entries.length} baseline migration(s) as applied (no SQL executed)`,
|
||||
);
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) {
|
||||
console.error("[migrate] DATABASE_URL environment variable is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`[migrate] Migrations folder: ${migrationsFolder}`);
|
||||
|
||||
// Single connection for migration
|
||||
const sqlClient = postgres(databaseUrl, { max: 1 });
|
||||
|
||||
try {
|
||||
// Bootstrap existing databases before running the migrator
|
||||
await bootstrapExistingDb(sqlClient);
|
||||
|
||||
// Run Drizzle migrator (idempotent — applies only unapplied migrations)
|
||||
const db = drizzle(sqlClient);
|
||||
await migrate(db, { migrationsFolder });
|
||||
console.log("[migrate] Migration complete");
|
||||
|
||||
await sqlClient.end();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error("[migrate] Migration failed:", (err as Error).message);
|
||||
try {
|
||||
await sqlClient.end();
|
||||
} catch {
|
||||
// ignore close errors during failure
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Only auto-run when executed directly (not when imported in tests)
|
||||
const isMainModule =
|
||||
process.argv[1] &&
|
||||
(process.argv[1].endsWith("migrate.ts") || process.argv[1].endsWith("migrate.js"));
|
||||
|
||||
if (isMainModule) {
|
||||
run();
|
||||
}
|
||||
8
apps/api/start.sh
Executable file
8
apps/api/start.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "[start.sh] Running database migrations..."
|
||||
node --enable-source-maps apps/api/dist/database/migrate.js
|
||||
|
||||
echo "[start.sh] Starting API server..."
|
||||
exec node --enable-source-maps apps/api/dist/main.js
|
||||
@@ -354,6 +354,28 @@ catalogVehicles
|
||||
- `emex_*` — EMEX scraper cache (similar structure with translations)
|
||||
- `parts_catalogs_*` — PartsCatalogs API cache (defined in `parts-catalogs.ts`)
|
||||
|
||||
### Migration Workflow
|
||||
|
||||
**Strategy:** Formal Drizzle SQL migrations with automated startup.
|
||||
|
||||
**Schema change workflow:**
|
||||
1. Edit schema files in `apps/api/src/database/schema/`
|
||||
2. Run `pnpm db:generate` — produces a timestamped `.sql` file in `apps/api/drizzle/`
|
||||
3. Review the SQL diff in PR (the migration is version-controlled alongside code)
|
||||
4. Commit and deploy — migration runs automatically on container startup
|
||||
|
||||
**Container startup:** The API server runs `apps/api/start.sh` which executes `node dist/database/migrate.js` BEFORE starting the NestJS server. This ensures all pending migrations are applied before any code runs.
|
||||
|
||||
**Bootstrap note:** Existing databases with no migration history (`__drizzle_migrations` table missing) are automatically bootstrapped on first run. The bootstrap creates the tracking table and marks all baseline migrations as already applied (no SQL is executed against the live DB). This is safe for databases previously managed via `pnpm db:push`.
|
||||
|
||||
**Manual commands:**
|
||||
- `pnpm db:migrate` — Run migrations in dev (uses `tsx` for direct TS execution)
|
||||
- `pnpm db:migrate:dist` — Run migrations against compiled JS (for production manual override)
|
||||
- `pnpm db:generate` — Create a new migration from schema changes
|
||||
- `pnpm db:push` — Direct schema push (still available for local dev, discouraged for shared environments)
|
||||
|
||||
**Worker process:** The BullMQ worker overrides the Docker CMD in `docker-compose.coolify.yml` and does NOT run migrations. Only the API service handles migrations, preventing race conditions.
|
||||
|
||||
### Job Queues
|
||||
|
||||
**Framework:** BullMQ with Redis
|
||||
@@ -752,7 +774,7 @@ Dependencies: Radix UI (accordion, dialog, dropdown-menu, label, popover, select
|
||||
2. `git pull origin main`
|
||||
3. `pnpm install`
|
||||
4. `pnpm build`
|
||||
5. `pnpm db:push` (migrations)
|
||||
5. `pnpm db:migrate` (migrations)
|
||||
6. PM2 reload all
|
||||
|
||||
### PM2 Configuration
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -120,6 +120,9 @@ importers:
|
||||
dotenv:
|
||||
specifier: ^16.4.0
|
||||
version: 16.4.5
|
||||
drizzle-kit:
|
||||
specifier: ^0.31.4
|
||||
version: 0.31.9
|
||||
drizzle-orm:
|
||||
specifier: ^0.41.0
|
||||
version: 0.41.0(@opentelemetry/api@1.9.0)(@types/pg@8.15.6)(gel@2.2.0)(kysely@0.28.11)(postgres@3.4.8)
|
||||
@@ -169,9 +172,6 @@ importers:
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@22.19.11)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
drizzle-kit:
|
||||
specifier: ^0.31.4
|
||||
version: 0.31.9
|
||||
playwright:
|
||||
specifier: ^1.50.0
|
||||
version: 1.58.2
|
||||
|
||||
Reference in New Issue
Block a user