0a3aaa5798
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>
66 lines
4.3 KiB
SQL
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"); |