feat: ST-001–ST-005 foundation — scaffolding, Prisma, schema, seed, env

- ST-001: Next.js 16 + React 19 + TypeScript strict + Tailwind + shadcn/ui + all deps
- ST-002: Prisma v7 setup with better-sqlite3 adapter (local) and pg adapter (cloud)
- ST-003: Full schema migration (users, pbis, stories, sprints, tasks, todos, api_tokens)
- ST-004: Seed with 9 PBIs, ~40 stories, demo user (demo/demo1234), lars user
- ST-005: Zod-validated env vars, .env.example, lib/session, lib/auth, lib/api-auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-04-22 21:04:48 +02:00
parent 4cf5833c1d
commit 7f94bb6359
32 changed files with 8653 additions and 183 deletions

View file

@ -0,0 +1,171 @@
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL PRIMARY KEY,
"username" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"is_demo" BOOLEAN NOT NULL DEFAULT false,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "user_roles" (
"id" TEXT NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"role" TEXT NOT NULL,
CONSTRAINT "user_roles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "api_tokens" (
"id" TEXT NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"token_hash" TEXT NOT NULL,
"label" TEXT,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"revoked_at" DATETIME,
CONSTRAINT "api_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "products" (
"id" TEXT NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"repo_url" TEXT,
"definition_of_done" TEXT NOT NULL,
"archived" BOOLEAN NOT NULL DEFAULT false,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL,
CONSTRAINT "products_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "pbis" (
"id" TEXT NOT NULL PRIMARY KEY,
"product_id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"priority" INTEGER NOT NULL,
"sort_order" REAL NOT NULL,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL,
CONSTRAINT "pbis_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "products" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "stories" (
"id" TEXT NOT NULL PRIMARY KEY,
"pbi_id" TEXT NOT NULL,
"product_id" TEXT NOT NULL,
"sprint_id" TEXT,
"title" TEXT NOT NULL,
"description" TEXT,
"acceptance_criteria" TEXT,
"priority" INTEGER NOT NULL,
"sort_order" REAL NOT NULL,
"status" TEXT NOT NULL DEFAULT 'OPEN',
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL,
CONSTRAINT "stories_pbi_id_fkey" FOREIGN KEY ("pbi_id") REFERENCES "pbis" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "stories_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "products" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "stories_sprint_id_fkey" FOREIGN KEY ("sprint_id") REFERENCES "sprints" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "story_logs" (
"id" TEXT NOT NULL PRIMARY KEY,
"story_id" TEXT NOT NULL,
"type" TEXT NOT NULL,
"content" TEXT NOT NULL,
"status" TEXT,
"commit_hash" TEXT,
"commit_message" TEXT,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "story_logs_story_id_fkey" FOREIGN KEY ("story_id") REFERENCES "stories" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "sprints" (
"id" TEXT NOT NULL PRIMARY KEY,
"product_id" TEXT NOT NULL,
"sprint_goal" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"completed_at" DATETIME,
CONSTRAINT "sprints_product_id_fkey" FOREIGN KEY ("product_id") REFERENCES "products" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "tasks" (
"id" TEXT NOT NULL PRIMARY KEY,
"story_id" TEXT NOT NULL,
"sprint_id" TEXT,
"title" TEXT NOT NULL,
"description" TEXT,
"priority" INTEGER NOT NULL,
"sort_order" REAL NOT NULL,
"status" TEXT NOT NULL DEFAULT 'TO_DO',
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL,
CONSTRAINT "tasks_story_id_fkey" FOREIGN KEY ("story_id") REFERENCES "stories" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "tasks_sprint_id_fkey" FOREIGN KEY ("sprint_id") REFERENCES "sprints" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "todos" (
"id" TEXT NOT NULL PRIMARY KEY,
"user_id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"done" BOOLEAN NOT NULL DEFAULT false,
"archived" BOOLEAN NOT NULL DEFAULT false,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" DATETIME NOT NULL,
CONSTRAINT "todos_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
-- CreateIndex
CREATE UNIQUE INDEX "user_roles_user_id_role_key" ON "user_roles"("user_id", "role");
-- CreateIndex
CREATE UNIQUE INDEX "api_tokens_token_hash_key" ON "api_tokens"("token_hash");
-- CreateIndex
CREATE INDEX "api_tokens_token_hash_idx" ON "api_tokens"("token_hash");
-- CreateIndex
CREATE INDEX "products_user_id_archived_idx" ON "products"("user_id", "archived");
-- CreateIndex
CREATE UNIQUE INDEX "products_user_id_name_key" ON "products"("user_id", "name");
-- CreateIndex
CREATE INDEX "pbis_product_id_priority_sort_order_idx" ON "pbis"("product_id", "priority", "sort_order");
-- CreateIndex
CREATE INDEX "stories_pbi_id_priority_sort_order_idx" ON "stories"("pbi_id", "priority", "sort_order");
-- CreateIndex
CREATE INDEX "stories_sprint_id_sort_order_idx" ON "stories"("sprint_id", "sort_order");
-- CreateIndex
CREATE INDEX "stories_product_id_status_idx" ON "stories"("product_id", "status");
-- CreateIndex
CREATE INDEX "story_logs_story_id_created_at_idx" ON "story_logs"("story_id", "created_at");
-- CreateIndex
CREATE INDEX "sprints_product_id_status_idx" ON "sprints"("product_id", "status");
-- CreateIndex
CREATE INDEX "tasks_story_id_priority_sort_order_idx" ON "tasks"("story_id", "priority", "sort_order");
-- CreateIndex
CREATE INDEX "tasks_sprint_id_status_idx" ON "tasks"("sprint_id", "status");
-- CreateIndex
CREATE INDEX "todos_user_id_done_archived_idx" ON "todos"("user_id", "done", "archived");

View file

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"