diff --git a/prisma/migrations/20260429165857_add_claude_job/migration.sql b/prisma/migrations/20260429165857_add_claude_job/migration.sql new file mode 100644 index 0000000..7360c3f --- /dev/null +++ b/prisma/migrations/20260429165857_add_claude_job/migration.sql @@ -0,0 +1,43 @@ +-- CreateEnum +CREATE TYPE "ClaudeJobStatus" AS ENUM ('QUEUED', 'CLAIMED', 'RUNNING', 'DONE', 'FAILED', 'CANCELLED'); + +-- CreateTable +CREATE TABLE "claude_jobs" ( + "id" TEXT NOT NULL, + "user_id" TEXT NOT NULL, + "product_id" TEXT NOT NULL, + "task_id" TEXT NOT NULL, + "status" "ClaudeJobStatus" NOT NULL DEFAULT 'QUEUED', + "claimed_by_token_id" TEXT, + "claimed_at" TIMESTAMP(3), + "started_at" TIMESTAMP(3), + "finished_at" TIMESTAMP(3), + "branch" TEXT, + "summary" TEXT, + "error" TEXT, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "claude_jobs_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "claude_jobs_user_id_status_idx" ON "claude_jobs"("user_id", "status"); + +-- CreateIndex +CREATE INDEX "claude_jobs_task_id_status_idx" ON "claude_jobs"("task_id", "status"); + +-- CreateIndex +CREATE INDEX "claude_jobs_status_claimed_at_idx" ON "claude_jobs"("status", "claimed_at"); + +-- AddForeignKey +ALTER TABLE "claude_jobs" ADD CONSTRAINT "claude_jobs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "claude_jobs" ADD CONSTRAINT "claude_jobs_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "products"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "claude_jobs" ADD CONSTRAINT "claude_jobs_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "claude_jobs" ADD CONSTRAINT "claude_jobs_claimed_by_token_id_fkey" FOREIGN KEY ("claimed_by_token_id") REFERENCES "api_tokens"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 07c5816..03d8eb0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -29,6 +29,15 @@ enum PbiStatus { DONE } +enum ClaudeJobStatus { + QUEUED + CLAIMED + RUNNING + DONE + FAILED + CANCELLED +} + enum TaskStatus { TO_DO IN_PROGRESS @@ -66,14 +75,15 @@ model User { created_at DateTime @default(now()) updated_at DateTime @updatedAt roles UserRole[] - api_tokens ApiToken[] - products Product[] - todos Todo[] - product_members ProductMember[] - assigned_stories Story[] @relation("StoryAssignee") - login_pairings LoginPairing[] + api_tokens ApiToken[] + products Product[] + todos Todo[] + product_members ProductMember[] + assigned_stories Story[] @relation("StoryAssignee") + login_pairings LoginPairing[] asked_questions ClaudeQuestion[] @relation("ClaudeQuestionAsker") answered_questions ClaudeQuestion[] @relation("ClaudeQuestionAnswerer") + claude_jobs ClaudeJob[] @@index([active_product_id]) @@map("users") @@ -90,13 +100,14 @@ model UserRole { } model ApiToken { - id String @id @default(cuid()) - user User @relation(fields: [user_id], references: [id], onDelete: Cascade) - user_id String - token_hash String @unique - label String? - created_at DateTime @default(now()) - revoked_at DateTime? + id String @id @default(cuid()) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) + user_id String + token_hash String @unique + label String? + created_at DateTime @default(now()) + revoked_at DateTime? + claimed_jobs ClaudeJob[] @@index([token_hash]) @@map("api_tokens") @@ -119,8 +130,9 @@ model Product { stories Story[] todos Todo[] members ProductMember[] - active_for_users User[] @relation("UserActiveProduct") + active_for_users User[] @relation("UserActiveProduct") claude_questions ClaudeQuestion[] + claude_jobs ClaudeJob[] @@unique([user_id, name]) @@unique([user_id, code]) @@ -225,12 +237,39 @@ model Task { created_at DateTime @default(now()) updated_at DateTime @updatedAt claude_questions ClaudeQuestion[] + claude_jobs ClaudeJob[] @@index([story_id, priority, sort_order]) @@index([sprint_id, status]) @@map("tasks") } +model ClaudeJob { + id String @id @default(cuid()) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) + user_id String + product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) + product_id String + task Task @relation(fields: [task_id], references: [id], onDelete: Cascade) + task_id String + status ClaudeJobStatus @default(QUEUED) + claimed_by_token ApiToken? @relation(fields: [claimed_by_token_id], references: [id], onDelete: SetNull) + claimed_by_token_id String? + claimed_at DateTime? + started_at DateTime? + finished_at DateTime? + branch String? + summary String? + error String? + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@index([user_id, status]) + @@index([task_id, status]) + @@index([status, claimed_at]) + @@map("claude_jobs") +} + model ProductMember { id String @id @default(cuid()) product Product @relation(fields: [product_id], references: [id], onDelete: Cascade)