db(cards): baseline migration + drizzle-tracking bootstrap script
Some checks are pending
CI / validate (push) Waiting to run

Schließt die Ops-Lücke „kein versioniertes Schema-Tracking" aus
FEATURE_IDEAS.md.

* apps/api/src/db/migrations/0000_baseline.sql — Drizzle-generierte
  Baseline-Migration, 355 Zeilen, 25 Tabellen + 5 Enums (cards- und
  marketplace-Schema). Eingefrostet auf den Live-Stand 2026-05-12.
* apps/api/scripts/bootstrap-drizzle-tracking.ts — neues Script,
  markiert die Baseline in einer bestehenden DB als „bereits
  angewandt", ohne SQL erneut auszuführen. Verwendet sha256 wie
  drizzle-orm/migrator (Hash 312d67ba1aeb…), idempotent.
* package.json: drizzle:migrate + drizzle:bootstrap-tracking
  npm-scripts.
* docs/playbooks/DRIZZLE_MIGRATIONS_BOOTSTRAP.md — Hand-Over für
  Prod (Bootstrap einmalig, dann normaler Workflow:
  schema → generate → commit → migrate, kein push --force mehr).

Lokal verifiziert: 17/104 Tests grün, bootstrap idempotent,
drizzle-kit migrate erkennt die Baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-12 18:53:52 +02:00
parent 5a29dd9a8c
commit 4bb1390180
7 changed files with 3523 additions and 5 deletions

View file

@ -14,8 +14,10 @@
"lint": "echo 'lint configured later (eslint flat-config)'",
"clean": "rm -rf dist .turbo coverage",
"drizzle:generate": "drizzle-kit generate",
"drizzle:migrate": "drizzle-kit migrate",
"drizzle:push": "drizzle-kit push --force",
"drizzle:studio": "drizzle-kit studio"
"drizzle:studio": "drizzle-kit studio",
"drizzle:bootstrap-tracking": "bun run scripts/bootstrap-drizzle-tracking.ts"
},
"dependencies": {
"@cards/domain": "workspace:*",

View file

@ -0,0 +1,90 @@
/**
* Bootstrap-Script für drizzle.__drizzle_migrations.
*
* Eine Cards-DB, die bisher über `drizzle-kit push` (Schema-Sync ohne
* Migrations-Tracking) gepflegt wurde, hat das Drizzle-Tracking-Schema
* nicht. Dieses Script holt das nach:
*
* 1. Erstellt `drizzle.__drizzle_migrations` (idempotent).
* 2. Liest `src/db/migrations/meta/_journal.json`.
* 3. Markiert jede dort gelistete Migration als bereits angewandt",
* mit dem gleichen Hash, den `drizzle-orm/migrator` selbst
* berechnen würde sha256(file_content) als hex.
*
* Nutzung:
* DATABASE_URL=postgresql://… pnpm bootstrap:drizzle
*
* Idempotent: ein zweiter Run macht nichts neu, sondern überspringt
* Migrations, deren Hash schon eingetragen ist.
*
* Nach dem Bootstrap wird `drizzle-kit migrate` jede Migration aus
* dem Journal als bekannt erkennen und keine SQL erneut ausführen.
* Künftige Schema-Änderungen `drizzle-kit generate` commit
* `drizzle-kit migrate` führt nur die neuen Migrations aus.
*/
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import postgres from 'postgres';
const url = process.env.DATABASE_URL;
if (!url) {
console.error('DATABASE_URL not set');
process.exit(1);
}
const here = dirname(fileURLToPath(import.meta.url));
const migrationsDir = resolve(here, '..', 'src', 'db', 'migrations');
const journalPath = resolve(migrationsDir, 'meta', '_journal.json');
interface JournalEntry {
idx: number;
version: string;
when: number;
tag: string;
breakpoints: boolean;
}
interface Journal {
version: string;
dialect: string;
entries: JournalEntry[];
}
const journal: Journal = JSON.parse(readFileSync(journalPath, 'utf-8'));
const sql = postgres(url, { max: 1 });
try {
await sql`CREATE SCHEMA IF NOT EXISTS drizzle`;
await sql`CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
id SERIAL PRIMARY KEY,
hash text NOT NULL,
created_at bigint
)`;
const existing = await sql<{ hash: string }[]>`SELECT hash FROM drizzle.__drizzle_migrations`;
const existingHashes = new Set(existing.map((r) => r.hash));
for (const entry of journal.entries) {
const migrationPath = resolve(migrationsDir, `${entry.tag}.sql`);
const fileContent = readFileSync(migrationPath, 'utf-8');
const hash = createHash('sha256').update(fileContent).digest('hex');
if (existingHashes.has(hash)) {
console.log(`SKIP ${entry.tag} (hash ${hash.slice(0, 12)}… already tracked)`);
continue;
}
await sql`INSERT INTO drizzle.__drizzle_migrations ("hash", "created_at")
VALUES (${hash}, ${entry.when})`;
console.log(`MARKED ${entry.tag} (hash ${hash.slice(0, 12)}…)`);
}
const final = await sql<{ count: number }[]>`SELECT COUNT(*)::int AS count
FROM drizzle.__drizzle_migrations`;
console.log(`\nTracking-Tabelle hat jetzt ${final[0]!.count} Eintrag/Einträge.`);
} finally {
await sql.end({ timeout: 5 });
}

View file

@ -0,0 +1,356 @@
CREATE SCHEMA "cards";
--> statement-breakpoint
CREATE SCHEMA "marketplace";
--> statement-breakpoint
CREATE TYPE "marketplace"."ai_mod_verdict" AS ENUM('pass', 'flag', 'block');--> statement-breakpoint
CREATE TYPE "marketplace"."card_type" AS ENUM('basic', 'basic-reverse', 'cloze', 'type-in', 'image-occlusion', 'audio', 'multiple-choice');--> statement-breakpoint
CREATE TYPE "marketplace"."pr_status" AS ENUM('open', 'merged', 'closed', 'rejected');--> statement-breakpoint
CREATE TYPE "marketplace"."report_category" AS ENUM('spam', 'copyright', 'nsfw', 'misinformation', 'hate', 'other');--> statement-breakpoint
CREATE TYPE "marketplace"."report_status" AS ENUM('open', 'dismissed', 'actioned');--> statement-breakpoint
CREATE TABLE "cards"."card_tags" (
"card_id" text NOT NULL,
"tag_id" text NOT NULL,
CONSTRAINT "card_tags_card_id_tag_id_pk" PRIMARY KEY("card_id","tag_id")
);
--> statement-breakpoint
CREATE TABLE "cards"."cards" (
"id" text PRIMARY KEY NOT NULL,
"deck_id" text NOT NULL,
"user_id" text NOT NULL,
"type" text NOT NULL,
"fields" jsonb NOT NULL,
"media_refs" jsonb DEFAULT '[]'::jsonb NOT NULL,
"content_hash" 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 "cards"."decks" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"name" text NOT NULL,
"description" text,
"color" text,
"category" text,
"visibility" text DEFAULT 'private' NOT NULL,
"fsrs_settings" jsonb DEFAULT '{}'::jsonb NOT NULL,
"content_hash" text,
"forked_from_marketplace_deck_id" text,
"forked_from_marketplace_version_id" text,
"archived_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 "cards"."import_jobs" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"source" text NOT NULL,
"state" text DEFAULT 'queued' NOT NULL,
"meta" jsonb,
"error" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"finished_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "marketplace"."ai_moderation_log" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"version_id" uuid NOT NULL,
"verdict" "marketplace"."ai_mod_verdict" NOT NULL,
"categories" text[],
"model" text,
"rationale" text,
"human_reviewed" boolean DEFAULT false NOT NULL,
"human_overrode" boolean DEFAULT false NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."author_follows" (
"follower_user_id" text NOT NULL,
"author_user_id" text NOT NULL,
"since" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."author_payouts" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"author_user_id" text NOT NULL,
"source_purchase_id" uuid NOT NULL,
"credits_granted" integer NOT NULL,
"credits_grant_id" text,
"granted_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."authors" (
"user_id" text PRIMARY KEY NOT NULL,
"slug" text NOT NULL,
"display_name" text NOT NULL,
"bio" text,
"avatar_url" text,
"joined_at" timestamp with time zone DEFAULT now() NOT NULL,
"pseudonym" boolean DEFAULT false NOT NULL,
"verified_mana" boolean DEFAULT false NOT NULL,
"verified_community" boolean DEFAULT false NOT NULL,
"banned_at" timestamp with time zone,
"banned_reason" text
);
--> statement-breakpoint
CREATE TABLE "marketplace"."card_discussions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"card_content_hash" text NOT NULL,
"deck_id" uuid NOT NULL,
"author_user_id" text NOT NULL,
"parent_id" uuid,
"body" text NOT NULL,
"hidden" boolean DEFAULT false NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_forks" (
"user_id" text NOT NULL,
"source_deck_id" uuid NOT NULL,
"source_version_id" uuid NOT NULL,
"forked_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_pull_requests" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"deck_id" uuid NOT NULL,
"author_user_id" text NOT NULL,
"status" "marketplace"."pr_status" DEFAULT 'open' NOT NULL,
"title" text NOT NULL,
"body" text,
"diff" jsonb NOT NULL,
"merged_into_version_id" uuid,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"resolved_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_purchases" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"buyer_user_id" text NOT NULL,
"deck_id" uuid NOT NULL,
"version_id" uuid NOT NULL,
"price_credits" integer NOT NULL,
"author_share" integer NOT NULL,
"mana_share" integer NOT NULL,
"credits_transaction" text,
"purchased_at" timestamp with time zone DEFAULT now() NOT NULL,
"refunded_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_reports" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"deck_id" uuid NOT NULL,
"version_id" uuid,
"card_content_hash" text,
"reporter_user_id" text NOT NULL,
"category" "marketplace"."report_category" NOT NULL,
"body" text,
"status" "marketplace"."report_status" DEFAULT 'open' NOT NULL,
"resolved_by" text,
"resolved_at" timestamp with time zone,
"resolution_notes" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_stars" (
"user_id" text NOT NULL,
"deck_id" uuid NOT NULL,
"starred_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_subscriptions" (
"user_id" text NOT NULL,
"deck_id" uuid NOT NULL,
"current_version_id" uuid,
"subscribed_at" timestamp with time zone DEFAULT now() NOT NULL,
"notify_updates" boolean DEFAULT true NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_tags" (
"deck_id" uuid NOT NULL,
"tag_id" uuid NOT NULL
);
--> statement-breakpoint
CREATE TABLE "cards"."media_files" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"object_key" text NOT NULL,
"mime_type" text NOT NULL,
"original_filename" text,
"size_bytes" integer NOT NULL,
"kind" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "cards"."media_refs" (
"id" text PRIMARY KEY NOT NULL,
"card_id" text NOT NULL,
"user_id" text NOT NULL,
"mana_media_object_id" text NOT NULL,
"kind" text NOT NULL,
"ord" integer DEFAULT 0 NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_cards" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"version_id" uuid NOT NULL,
"type" "marketplace"."card_type" NOT NULL,
"fields" jsonb NOT NULL,
"ord" integer NOT NULL,
"content_hash" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."deck_versions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"deck_id" uuid NOT NULL,
"semver" text NOT NULL,
"changelog" text,
"content_hash" text NOT NULL,
"card_count" integer NOT NULL,
"published_at" timestamp with time zone DEFAULT now() NOT NULL,
"deprecated_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "marketplace"."decks" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"slug" text NOT NULL,
"title" text NOT NULL,
"description" text,
"language" text,
"category" text,
"license" text DEFAULT 'Cardecky-Personal-Use-1.0' NOT NULL,
"price_credits" integer DEFAULT 0 NOT NULL,
"owner_user_id" text NOT NULL,
"latest_version_id" uuid,
"is_featured" boolean DEFAULT false NOT NULL,
"is_takedown" boolean DEFAULT false NOT NULL,
"takedown_at" timestamp with time zone,
"takedown_reason" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "decks_price_requires_license" CHECK (price_credits = 0 OR license = 'Cardecky-Pro-Only-1.0')
);
--> statement-breakpoint
CREATE TABLE "cards"."reviews" (
"card_id" text NOT NULL,
"sub_index" integer DEFAULT 0 NOT NULL,
"user_id" text NOT NULL,
"due" timestamp with time zone NOT NULL,
"stability" real NOT NULL,
"difficulty" real NOT NULL,
"elapsed_days" real DEFAULT 0 NOT NULL,
"scheduled_days" real DEFAULT 0 NOT NULL,
"learning_steps" integer DEFAULT 0 NOT NULL,
"reps" integer DEFAULT 0 NOT NULL,
"lapses" integer DEFAULT 0 NOT NULL,
"state" text DEFAULT 'new' NOT NULL,
"last_review" timestamp with time zone,
CONSTRAINT "reviews_card_id_sub_index_pk" PRIMARY KEY("card_id","sub_index")
);
--> statement-breakpoint
CREATE TABLE "cards"."study_sessions" (
"id" text PRIMARY KEY NOT NULL,
"user_id" text NOT NULL,
"deck_id" text NOT NULL,
"started_at" timestamp with time zone DEFAULT now() NOT NULL,
"finished_at" timestamp with time zone,
"cards_reviewed" integer DEFAULT 0 NOT NULL,
"cards_correct" integer DEFAULT 0 NOT NULL
);
--> statement-breakpoint
CREATE TABLE "marketplace"."tag_definitions" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"slug" text NOT NULL,
"name" text NOT NULL,
"parent_id" uuid,
"description" text,
"curated" boolean DEFAULT false NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "cards"."tags" (
"id" text PRIMARY KEY NOT NULL,
"deck_id" text NOT NULL,
"user_id" text NOT NULL,
"name" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "cards"."card_tags" ADD CONSTRAINT "card_tags_card_id_cards_id_fk" FOREIGN KEY ("card_id") REFERENCES "cards"."cards"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "cards"."cards" ADD CONSTRAINT "cards_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "cards"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."ai_moderation_log" ADD CONSTRAINT "ai_moderation_log_version_id_deck_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."author_follows" ADD CONSTRAINT "author_follows_author_user_id_authors_user_id_fk" FOREIGN KEY ("author_user_id") REFERENCES "marketplace"."authors"("user_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."author_payouts" ADD CONSTRAINT "author_payouts_author_user_id_authors_user_id_fk" FOREIGN KEY ("author_user_id") REFERENCES "marketplace"."authors"("user_id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."author_payouts" ADD CONSTRAINT "author_payouts_source_purchase_id_deck_purchases_id_fk" FOREIGN KEY ("source_purchase_id") REFERENCES "marketplace"."deck_purchases"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."card_discussions" ADD CONSTRAINT "card_discussions_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_forks" ADD CONSTRAINT "deck_forks_source_deck_id_decks_id_fk" FOREIGN KEY ("source_deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_forks" ADD CONSTRAINT "deck_forks_source_version_id_deck_versions_id_fk" FOREIGN KEY ("source_version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_pull_requests" ADD CONSTRAINT "deck_pull_requests_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_pull_requests" ADD CONSTRAINT "deck_pull_requests_merged_into_version_id_deck_versions_id_fk" FOREIGN KEY ("merged_into_version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_purchases" ADD CONSTRAINT "deck_purchases_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_purchases" ADD CONSTRAINT "deck_purchases_version_id_deck_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_reports" ADD CONSTRAINT "deck_reports_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_reports" ADD CONSTRAINT "deck_reports_version_id_deck_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_stars" ADD CONSTRAINT "deck_stars_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_subscriptions" ADD CONSTRAINT "deck_subscriptions_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_subscriptions" ADD CONSTRAINT "deck_subscriptions_current_version_id_deck_versions_id_fk" FOREIGN KEY ("current_version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_tags" ADD CONSTRAINT "deck_tags_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_tags" ADD CONSTRAINT "deck_tags_tag_id_tag_definitions_id_fk" FOREIGN KEY ("tag_id") REFERENCES "marketplace"."tag_definitions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "cards"."media_refs" ADD CONSTRAINT "media_refs_card_id_cards_id_fk" FOREIGN KEY ("card_id") REFERENCES "cards"."cards"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_cards" ADD CONSTRAINT "deck_cards_version_id_deck_versions_id_fk" FOREIGN KEY ("version_id") REFERENCES "marketplace"."deck_versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."deck_versions" ADD CONSTRAINT "deck_versions_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "marketplace"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "marketplace"."decks" ADD CONSTRAINT "decks_owner_user_id_authors_user_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "marketplace"."authors"("user_id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "cards"."reviews" ADD CONSTRAINT "reviews_card_id_cards_id_fk" FOREIGN KEY ("card_id") REFERENCES "cards"."cards"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "cards"."study_sessions" ADD CONSTRAINT "study_sessions_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "cards"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "cards"."tags" ADD CONSTRAINT "tags_deck_id_decks_id_fk" FOREIGN KEY ("deck_id") REFERENCES "cards"."decks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "cards_deck_idx" ON "cards"."cards" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "cards_user_idx" ON "cards"."cards" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "decks_user_idx" ON "cards"."decks" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "imports_user_idx" ON "cards"."import_jobs" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "imports_state_idx" ON "cards"."import_jobs" USING btree ("state");--> statement-breakpoint
CREATE INDEX "ai_moderation_log_version_idx" ON "marketplace"."ai_moderation_log" USING btree ("version_id");--> statement-breakpoint
CREATE INDEX "ai_moderation_log_verdict_idx" ON "marketplace"."ai_moderation_log" USING btree ("verdict");--> statement-breakpoint
CREATE UNIQUE INDEX "author_follows_pk" ON "marketplace"."author_follows" USING btree ("follower_user_id","author_user_id");--> statement-breakpoint
CREATE INDEX "author_follows_author_idx" ON "marketplace"."author_follows" USING btree ("author_user_id");--> statement-breakpoint
CREATE INDEX "author_follows_follower_idx" ON "marketplace"."author_follows" USING btree ("follower_user_id");--> statement-breakpoint
CREATE INDEX "author_payouts_author_idx" ON "marketplace"."author_payouts" USING btree ("author_user_id");--> statement-breakpoint
CREATE INDEX "author_payouts_purchase_idx" ON "marketplace"."author_payouts" USING btree ("source_purchase_id");--> statement-breakpoint
CREATE UNIQUE INDEX "authors_slug_idx" ON "marketplace"."authors" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "authors_verified_idx" ON "marketplace"."authors" USING btree ("verified_mana","verified_community");--> statement-breakpoint
CREATE INDEX "card_discussions_hash_idx" ON "marketplace"."card_discussions" USING btree ("card_content_hash");--> statement-breakpoint
CREATE INDEX "card_discussions_deck_idx" ON "marketplace"."card_discussions" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "card_discussions_parent_idx" ON "marketplace"."card_discussions" USING btree ("parent_id");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_forks_pk" ON "marketplace"."deck_forks" USING btree ("user_id","source_deck_id","source_version_id");--> statement-breakpoint
CREATE INDEX "deck_forks_source_idx" ON "marketplace"."deck_forks" USING btree ("source_deck_id");--> statement-breakpoint
CREATE INDEX "deck_pull_requests_deck_idx" ON "marketplace"."deck_pull_requests" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "deck_pull_requests_status_idx" ON "marketplace"."deck_pull_requests" USING btree ("deck_id","status");--> statement-breakpoint
CREATE INDEX "deck_pull_requests_author_idx" ON "marketplace"."deck_pull_requests" USING btree ("author_user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_purchases_buyer_deck_idx" ON "marketplace"."deck_purchases" USING btree ("buyer_user_id","deck_id");--> statement-breakpoint
CREATE INDEX "deck_purchases_buyer_idx" ON "marketplace"."deck_purchases" USING btree ("buyer_user_id");--> statement-breakpoint
CREATE INDEX "deck_purchases_deck_idx" ON "marketplace"."deck_purchases" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "deck_reports_deck_idx" ON "marketplace"."deck_reports" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "deck_reports_status_idx" ON "marketplace"."deck_reports" USING btree ("status");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_stars_pk" ON "marketplace"."deck_stars" USING btree ("user_id","deck_id");--> statement-breakpoint
CREATE INDEX "deck_stars_deck_idx" ON "marketplace"."deck_stars" USING btree ("deck_id");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_subscriptions_pk" ON "marketplace"."deck_subscriptions" USING btree ("user_id","deck_id");--> statement-breakpoint
CREATE INDEX "deck_subscriptions_deck_idx" ON "marketplace"."deck_subscriptions" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "deck_subscriptions_user_idx" ON "marketplace"."deck_subscriptions" USING btree ("user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_tags_pk" ON "marketplace"."deck_tags" USING btree ("deck_id","tag_id");--> statement-breakpoint
CREATE INDEX "deck_tags_tag_idx" ON "marketplace"."deck_tags" USING btree ("tag_id");--> statement-breakpoint
CREATE INDEX "media_files_user_idx" ON "cards"."media_files" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "media_card_idx" ON "cards"."media_refs" USING btree ("card_id");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_cards_version_ord_idx" ON "marketplace"."deck_cards" USING btree ("version_id","ord");--> statement-breakpoint
CREATE INDEX "deck_cards_hash_idx" ON "marketplace"."deck_cards" USING btree ("content_hash");--> statement-breakpoint
CREATE UNIQUE INDEX "deck_versions_deck_semver_idx" ON "marketplace"."deck_versions" USING btree ("deck_id","semver");--> statement-breakpoint
CREATE INDEX "deck_versions_deck_idx" ON "marketplace"."deck_versions" USING btree ("deck_id");--> statement-breakpoint
CREATE INDEX "deck_versions_hash_idx" ON "marketplace"."deck_versions" USING btree ("content_hash");--> statement-breakpoint
CREATE UNIQUE INDEX "decks_slug_idx" ON "marketplace"."decks" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "decks_owner_idx" ON "marketplace"."decks" USING btree ("owner_user_id");--> statement-breakpoint
CREATE INDEX "decks_featured_idx" ON "marketplace"."decks" USING btree ("is_featured");--> statement-breakpoint
CREATE INDEX "reviews_user_due_idx" ON "cards"."reviews" USING btree ("user_id","due");--> statement-breakpoint
CREATE INDEX "sessions_user_started_idx" ON "cards"."study_sessions" USING btree ("user_id","started_at");--> statement-breakpoint
CREATE UNIQUE INDEX "tag_definitions_slug_idx" ON "marketplace"."tag_definitions" USING btree ("slug");--> statement-breakpoint
CREATE INDEX "tag_definitions_parent_idx" ON "marketplace"."tag_definitions" USING btree ("parent_id");--> statement-breakpoint
CREATE INDEX "tags_deck_idx" ON "cards"."tags" USING btree ("deck_id");--> statement-breakpoint
CREATE UNIQUE INDEX "tags_deck_name_uniq" ON "cards"."tags" USING btree ("deck_id","name");

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1778604624860,
"tag": "0000_baseline",
"breakpoints": true
}
]
}