Files
buildfor_life_ops/drizzle/0000_init.sql
T
grabowski 0a3aaa5798 Phase 0 scaffold: SvelteKit 5 + Drizzle + auth + storage interface
Stack matches sibling buildfor_life_* apps: SvelteKit 5 with adapter-node,
Svelte 5 runes, TypeScript, Tailwind v4 with @theme inline tokens,
PostgreSQL via Drizzle ORM, Argon2id sessions via @node-rs/argon2 and
@oslojs/crypto, EasyMDE ready for wiki/decision markdown, Sharp for
thumbnails.

Included in this commit:
- Config: package.json, svelte.config.js, vite.config.ts, tsconfig.json,
  drizzle.config.ts, .gitignore, .env.example, .gitattributes, .npmrc
- Tenancy schema: companies, users, company_users, sessions
  (10 enums pre-declared for the full domain so downstream migrations
   don't re-diff them; decision_scope widened to include asset +
   work_package per product decision)
- Auth: password hashing + SHA-256-hashed session cookies,
  session lifetime 30d with sliding renewal at T-15d,
  login + logout + session refresh in hooks
- Storage: StorageAdapter interface + LocalDiskStorage with HMAC-signed
  URLs served by /api/files, S3 drop-in with zero schema change
- UI shell: dark-mode bootstrap in app.html identical to siblings,
  sidebar (w-64, h-14 header, amber attention band pattern from repair),
  topbar with breadcrumbs, theme toggle with cross-tab sync via
  storage event, blue-600 primary, responsive drawer
- Routes: (app) authed group with auto-redirect to /login,
  (auth) login group, dashboard placeholder, error page, signed-file API
- Scripts: create-user script for bootstrapping first admin user
- Drizzle: initial migration generated (0000_init.sql)
- Shared agents and skills committed under .claude/; per-user
  permissions gitignored

Typecheck: 0 errors / 0 warnings across 555 files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:38:14 +07:00

66 lines
4.3 KiB
SQL

CREATE TYPE "public"."audit_action" AS ENUM('create', 'update', 'delete', 'move', 'assign', 'complete', 'login', 'logout');--> statement-breakpoint
CREATE TYPE "public"."checklist_scope" AS ENUM('task', 'subtask', 'maintenance_event', 'ad_hoc');--> statement-breakpoint
CREATE TYPE "public"."container_kind" AS ENUM('project', 'property');--> statement-breakpoint
CREATE TYPE "public"."decision_scope" AS ENUM('project', 'property', 'asset', 'work_package');--> statement-breakpoint
CREATE TYPE "public"."doc_scope" AS ENUM('project', 'property', 'asset', 'work_package', 'decision_event');--> statement-breakpoint
CREATE TYPE "public"."field_type" AS ENUM('text', 'textarea', 'int', 'float', 'bool', 'date', 'ip', 'cidr', 'mac', 'enum', 'multi_enum', 'url', 'email', 'asset_ref');--> statement-breakpoint
CREATE TYPE "public"."interval_unit" AS ENUM('days', 'months', 'years', 'hours', 'cycles', 'km');--> statement-breakpoint
CREATE TYPE "public"."role" AS ENUM('admin', 'manager', 'user', 'viewer');--> statement-breakpoint
CREATE TYPE "public"."schedule_kind" AS ENUM('time', 'usage');--> statement-breakpoint
CREATE TYPE "public"."task_status" AS ENUM('todo', 'doing', 'done', 'blocked');--> statement-breakpoint
CREATE TYPE "public"."wiki_scope" AS ENUM('global', 'project', 'property');--> statement-breakpoint
CREATE TABLE "companies" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar(255) NOT NULL,
"slug" varchar(128) NOT NULL,
"settings_json" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
CONSTRAINT "companies_slug_unique" UNIQUE("slug")
);
--> statement-breakpoint
CREATE TABLE "company_users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"company_id" uuid NOT NULL,
"user_id" uuid NOT NULL,
"role" "role" DEFAULT 'user' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "sessions" (
"id" varchar(128) PRIMARY KEY NOT NULL,
"user_id" uuid NOT NULL,
"active_company_id" uuid,
"user_agent" text,
"ip" varchar(64),
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"last_seen_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"email" varchar(320) NOT NULL,
"email_normalized" varchar(320) NOT NULL,
"display_name" varchar(255) NOT NULL,
"password_hash" text,
"oidc_subject" varchar(255),
"oidc_issuer" varchar(255),
"is_active" boolean DEFAULT true NOT NULL,
"last_login_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,
"deleted_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "company_users" ADD CONSTRAINT "company_users_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "company_users" ADD CONSTRAINT "company_users_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("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 "sessions" ADD CONSTRAINT "sessions_active_company_id_companies_id_fk" FOREIGN KEY ("active_company_id") REFERENCES "public"."companies"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "company_users_uq" ON "company_users" USING btree ("company_id","user_id");--> statement-breakpoint
CREATE INDEX "company_users_by_user" ON "company_users" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "sessions_by_user" ON "sessions" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "sessions_by_expiry" ON "sessions" USING btree ("expires_at");--> statement-breakpoint
CREATE UNIQUE INDEX "users_email_norm_uq" ON "users" USING btree ("email_normalized");--> statement-breakpoint
CREATE UNIQUE INDEX "users_oidc_uq" ON "users" USING btree ("oidc_issuer","oidc_subject");