From 65d13933bfbc7195e6ca7e72283463ba57272cf5 Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Sat, 25 Apr 2026 11:53:10 +0800 Subject: [PATCH] feat(db): add hardware seed script, db:seed command, and initial migration - Create lib/db/seed.ts with idempotent hardware seeding - Add db:seed script and fix db:* scripts to load .env.local via bun --env-file - Generate initial Drizzle migration (0000_crazy_nick_fury) - Apply migration and seed 3 hardware devices (steamdeck-oled, steamdeck-lcd, steam-machine) --- drizzle/0000_crazy_nick_fury.sql | 80 +++++ drizzle/meta/0000_snapshot.json | 551 +++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 13 + lib/db/seed.ts | 47 +++ package.json | 9 +- 5 files changed, 696 insertions(+), 4 deletions(-) create mode 100644 drizzle/0000_crazy_nick_fury.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/_journal.json create mode 100644 lib/db/seed.ts diff --git a/drizzle/0000_crazy_nick_fury.sql b/drizzle/0000_crazy_nick_fury.sql new file mode 100644 index 0000000..8aec7dc --- /dev/null +++ b/drizzle/0000_crazy_nick_fury.sql @@ -0,0 +1,80 @@ +CREATE TYPE "public"."anti_cheat_status" AS ENUM('none', 'supported', 'unsupported', 'unknown');--> statement-breakpoint +CREATE TYPE "public"."proton_status" AS ENUM('native', 'proton', 'unsupported', 'unknown');--> statement-breakpoint +CREATE TYPE "public"."device_type" AS ENUM('handled', 'console');--> statement-breakpoint +CREATE TABLE "game_platform_support" ( + "id" text PRIMARY KEY NOT NULL, + "game_id" text NOT NULL, + "hardware_slug" text NOT NULL, + "is_supported" boolean DEFAULT false NOT NULL, + "proton_status" "proton_status" DEFAULT 'unknown' NOT NULL, + "anti_cheat_relevant" boolean DEFAULT false NOT NULL, + "anti_cheat_name" text, + "anti_cheat_version" text, + "anti_cheat_status" "anti_cheat_status" DEFAULT 'unknown' NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "game_hardware_unique" UNIQUE("game_id","hardware_slug") +); +--> statement-breakpoint +CREATE TABLE "game_versions" ( + "id" text PRIMARY KEY NOT NULL, + "game_id" text NOT NULL, + "build_id" text, + "version_string" text, + "is_latest" boolean DEFAULT false NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "game_build_unique" UNIQUE("game_id","build_id") +); +--> statement-breakpoint +CREATE TABLE "games" ( + "id" text PRIMARY KEY NOT NULL, + "steam_app_id" integer NOT NULL, + "title" text NOT NULL, + "description" text, + "publisher" text, + "developer" text, + "genres" jsonb, + "header_image" text, + "capsule_image" text, + "store_url" text, + "last_sync" timestamp, + "sync_status" text DEFAULT 'pending', + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "games_steam_app_id_unique" UNIQUE("steam_app_id") +); +--> statement-breakpoint +CREATE TABLE "hardware" ( + "slug" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "device_type" "device_type" NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "performance_entries" ( + "id" text PRIMARY KEY NOT NULL, + "version_id" text NOT NULL, + "hardware_slug" text NOT NULL, + "user_id" text NOT NULL, + "fps_avg" real NOT NULL, + "fps_low" real, + "fps_high" real, + "proton_version" text, + "os_version" text, + "is_fsr_enabled" boolean DEFAULT false NOT NULL, + "is_frame_gen_enabled" boolean DEFAULT false NOT NULL, + "load_time_ssd" real, + "load_time_sd" real, + "settings_json" jsonb, + "user_notes" text, + "is_removed" boolean DEFAULT false NOT NULL, + "removed_reason" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "game_platform_support" ADD CONSTRAINT "game_platform_support_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "game_platform_support" ADD CONSTRAINT "game_platform_support_hardware_slug_hardware_slug_fk" FOREIGN KEY ("hardware_slug") REFERENCES "public"."hardware"("slug") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "game_versions" ADD CONSTRAINT "game_versions_game_id_games_id_fk" FOREIGN KEY ("game_id") REFERENCES "public"."games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "performance_entries" ADD CONSTRAINT "performance_entries_version_id_game_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "public"."game_versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "performance_entries" ADD CONSTRAINT "performance_entries_hardware_slug_hardware_slug_fk" FOREIGN KEY ("hardware_slug") REFERENCES "public"."hardware"("slug") ON DELETE restrict ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..a072fd5 --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,551 @@ +{ + "id": "7ddf967c-480b-4279-aae8-a9bc7cd6bc51", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.game_platform_support": { + "name": "game_platform_support", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hardware_slug": { + "name": "hardware_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_supported": { + "name": "is_supported", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "proton_status": { + "name": "proton_status", + "type": "proton_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "anti_cheat_relevant": { + "name": "anti_cheat_relevant", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "anti_cheat_name": { + "name": "anti_cheat_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "anti_cheat_version": { + "name": "anti_cheat_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "anti_cheat_status": { + "name": "anti_cheat_status", + "type": "anti_cheat_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "game_platform_support_game_id_games_id_fk": { + "name": "game_platform_support_game_id_games_id_fk", + "tableFrom": "game_platform_support", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "game_platform_support_hardware_slug_hardware_slug_fk": { + "name": "game_platform_support_hardware_slug_hardware_slug_fk", + "tableFrom": "game_platform_support", + "tableTo": "hardware", + "columnsFrom": [ + "hardware_slug" + ], + "columnsTo": [ + "slug" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_hardware_unique": { + "name": "game_hardware_unique", + "nullsNotDistinct": false, + "columns": [ + "game_id", + "hardware_slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.game_versions": { + "name": "game_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "game_id": { + "name": "game_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_string": { + "name": "version_string", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_latest": { + "name": "is_latest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "game_versions_game_id_games_id_fk": { + "name": "game_versions_game_id_games_id_fk", + "tableFrom": "game_versions", + "tableTo": "games", + "columnsFrom": [ + "game_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "game_build_unique": { + "name": "game_build_unique", + "nullsNotDistinct": false, + "columns": [ + "game_id", + "build_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.games": { + "name": "games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "steam_app_id": { + "name": "steam_app_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "publisher": { + "name": "publisher", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "developer": { + "name": "developer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_image": { + "name": "header_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capsule_image": { + "name": "capsule_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "store_url": { + "name": "store_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync": { + "name": "last_sync", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "games_steam_app_id_unique": { + "name": "games_steam_app_id_unique", + "nullsNotDistinct": false, + "columns": [ + "steam_app_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hardware": { + "name": "hardware", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.performance_entries": { + "name": "performance_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version_id": { + "name": "version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hardware_slug": { + "name": "hardware_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fps_avg": { + "name": "fps_avg", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "fps_low": { + "name": "fps_low", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "fps_high": { + "name": "fps_high", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "proton_version": { + "name": "proton_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "os_version": { + "name": "os_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_fsr_enabled": { + "name": "is_fsr_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_frame_gen_enabled": { + "name": "is_frame_gen_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "load_time_ssd": { + "name": "load_time_ssd", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "load_time_sd": { + "name": "load_time_sd", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "user_notes": { + "name": "user_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_removed": { + "name": "is_removed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "removed_reason": { + "name": "removed_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "performance_entries_version_id_game_versions_id_fk": { + "name": "performance_entries_version_id_game_versions_id_fk", + "tableFrom": "performance_entries", + "tableTo": "game_versions", + "columnsFrom": [ + "version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "performance_entries_hardware_slug_hardware_slug_fk": { + "name": "performance_entries_hardware_slug_hardware_slug_fk", + "tableFrom": "performance_entries", + "tableTo": "hardware", + "columnsFrom": [ + "hardware_slug" + ], + "columnsTo": [ + "slug" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.anti_cheat_status": { + "name": "anti_cheat_status", + "schema": "public", + "values": [ + "none", + "supported", + "unsupported", + "unknown" + ] + }, + "public.proton_status": { + "name": "proton_status", + "schema": "public", + "values": [ + "native", + "proton", + "unsupported", + "unknown" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "handled", + "console" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..bd83048 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1777089104474, + "tag": "0000_crazy_nick_fury", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/lib/db/seed.ts b/lib/db/seed.ts new file mode 100644 index 0000000..d64ac3d --- /dev/null +++ b/lib/db/seed.ts @@ -0,0 +1,47 @@ +import "dotenv/config" +import { drizzle } from "drizzle-orm/node-postgres" +import { Pool } from "pg" +import { hardware } from "./schema/hardware" + +const pool = new Pool({ + connectionString: process.env.DATABASE_URL, +}) + +const db = drizzle(pool) + +async function seed() { + console.log("Seeding hardware table...") + + const devices = [ + { + slug: "steamdeck-oled", + name: "Steam Deck OLED", + deviceType: "handled" as const, + }, + { + slug: "steamdeck-lcd", + name: "Steam Deck LCD", + deviceType: "handled" as const, + }, + { + slug: "steam-machine", + name: "Steam Machine", + deviceType: "console" as const, + }, + ] + + for (const device of devices) { + await db + .insert(hardware) + .values(device) + .onConflictDoNothing({ target: hardware.slug }) + } + + console.log(`Seeded ${devices.length} hardware devices.`) + await pool.end() +} + +seed().catch((err) => { + console.error("Seed failed:", err) + process.exit(1) +}) diff --git a/package.json b/package.json index 6887b12..319f6fc 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,11 @@ "build": "next build", "start": "next start", "lint": "eslint", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - "db:studio": "drizzle-kit studio", - "db:push": "drizzle-kit push" + "db:generate": "bun --env-file=.env.local drizzle-kit generate", + "db:migrate": "bun --env-file=.env.local drizzle-kit migrate", + "db:studio": "bun --env-file=.env.local drizzle-kit studio", + "db:push": "bun --env-file=.env.local drizzle-kit push", + "db:seed": "bun run lib/db/seed.ts" }, "dependencies": { "@aws-sdk/client-s3": "^3.1036.0",