12
packages/db/drizzle.config.ts
Executable file
12
packages/db/drizzle.config.ts
Executable file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/schema.ts",
|
||||
out: "./drizzle",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url:
|
||||
process.env.DATABASE_URL ??
|
||||
"postgresql://adventureos:adventureos@localhost:5432/adventureos",
|
||||
},
|
||||
});
|
||||
184
packages/db/drizzle/0000_wealthy_patch.sql
Executable file
184
packages/db/drizzle/0000_wealthy_patch.sql
Executable file
@@ -0,0 +1,184 @@
|
||||
CREATE TABLE "achievements" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"key" varchar(100) NOT NULL,
|
||||
"unlocked_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "adventure_items" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"template_id" uuid NOT NULL,
|
||||
"type" varchar(20) NOT NULL,
|
||||
"label" varchar(200) NOT NULL,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"schedule" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "adventure_templates" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"days_of_week" integer[] NOT NULL,
|
||||
"is_default" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "ai_suggestions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"role" varchar(20) NOT NULL,
|
||||
"content" jsonb NOT NULL,
|
||||
"generated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"dismissed" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "books" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"title" varchar(300) NOT NULL,
|
||||
"author" varchar(200),
|
||||
"total_pages" integer DEFAULT 300 NOT NULL,
|
||||
"current_page" integer DEFAULT 0 NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'reading' NOT NULL,
|
||||
"started_at" timestamp with time zone DEFAULT now(),
|
||||
"finished_at" timestamp with time zone,
|
||||
"notes" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "daily_adventure_items" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"daily_adventure_id" uuid NOT NULL,
|
||||
"source_item_id" uuid,
|
||||
"type" varchar(20) NOT NULL,
|
||||
"label" varchar(200) NOT NULL,
|
||||
"state" varchar(20) DEFAULT 'blank' NOT NULL,
|
||||
"value" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"completed_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "daily_adventures" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"date" date NOT NULL,
|
||||
"template_id" uuid,
|
||||
"is_rest_day" boolean DEFAULT false NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "explorations" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"title" varchar(300) NOT NULL,
|
||||
"description" text NOT NULL,
|
||||
"category" varchar(50) NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'suggested' NOT NULL,
|
||||
"week_of" date NOT NULL,
|
||||
"completed_note" text,
|
||||
"accepted_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "reading_logs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"book_id" uuid NOT NULL,
|
||||
"date" date NOT NULL,
|
||||
"pages_read" integer DEFAULT 0 NOT NULL,
|
||||
"note" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "reflections" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"date" date NOT NULL,
|
||||
"went_well" text DEFAULT '' NOT NULL,
|
||||
"learned" text DEFAULT '' NOT NULL,
|
||||
"improve_tomorrow" text DEFAULT '' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "settings" (
|
||||
"user_id" uuid NOT NULL,
|
||||
"key" varchar(100) NOT NULL,
|
||||
"value" jsonb NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "spiritual_config" (
|
||||
"user_id" uuid PRIMARY KEY NOT NULL,
|
||||
"prayer_labels" text[] NOT NULL,
|
||||
"litany_labels" text[] NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "teacher_content" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"exploration_id" uuid,
|
||||
"topic" varchar(300) NOT NULL,
|
||||
"content" jsonb NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_progress" (
|
||||
"user_id" uuid PRIMARY KEY NOT NULL,
|
||||
"total_xp" integer DEFAULT 0 NOT NULL,
|
||||
"level" integer DEFAULT 1 NOT NULL,
|
||||
"current_chapter" varchar(100) DEFAULT 'Prologue: Awakening' NOT NULL,
|
||||
"grace_days_remaining" integer DEFAULT 2 NOT NULL,
|
||||
"consistency_score" integer DEFAULT 0 NOT NULL,
|
||||
"discipline_score" integer DEFAULT 0 NOT NULL,
|
||||
"learning_score" integer DEFAULT 0 NOT NULL,
|
||||
"spiritual_score" integer DEFAULT 0 NOT NULL,
|
||||
"health_score" integer DEFAULT 0 NOT NULL,
|
||||
"reading_score" integer DEFAULT 0 NOT NULL,
|
||||
"scores_updated_at" timestamp with time zone,
|
||||
"last_visit_date" date
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"display_name" varchar(100) DEFAULT 'Traveler' NOT NULL,
|
||||
"portrait_config" jsonb DEFAULT '{"skinTone":"#D4A574","hairColor":"#3D2314","clothingColor":"#3A6EA5"}'::jsonb NOT NULL,
|
||||
"current_title" varchar(100),
|
||||
"rest_days_used_week" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "weekly_reviews" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"week_start" date NOT NULL,
|
||||
"content" jsonb DEFAULT '{}'::jsonb NOT NULL,
|
||||
"mentor_letter" text,
|
||||
"xp_earned" integer DEFAULT 0 NOT NULL,
|
||||
"user_intention" text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "xp_events" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"date" date NOT NULL,
|
||||
"source" varchar(50) NOT NULL,
|
||||
"amount" integer NOT NULL,
|
||||
"metadata" jsonb
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "achievements" ADD CONSTRAINT "achievements_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "adventure_items" ADD CONSTRAINT "adventure_items_template_id_adventure_templates_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."adventure_templates"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "adventure_templates" ADD CONSTRAINT "adventure_templates_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "ai_suggestions" ADD CONSTRAINT "ai_suggestions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "books" ADD CONSTRAINT "books_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventure_items" ADD CONSTRAINT "daily_adventure_items_daily_adventure_id_daily_adventures_id_fk" FOREIGN KEY ("daily_adventure_id") REFERENCES "public"."daily_adventures"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD CONSTRAINT "daily_adventures_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD CONSTRAINT "daily_adventures_template_id_adventure_templates_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."adventure_templates"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "explorations" ADD CONSTRAINT "explorations_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "reading_logs" ADD CONSTRAINT "reading_logs_book_id_books_id_fk" FOREIGN KEY ("book_id") REFERENCES "public"."books"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "reflections" ADD CONSTRAINT "reflections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD CONSTRAINT "settings_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "spiritual_config" ADD CONSTRAINT "spiritual_config_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "teacher_content" ADD CONSTRAINT "teacher_content_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "teacher_content" ADD CONSTRAINT "teacher_content_exploration_id_explorations_id_fk" FOREIGN KEY ("exploration_id") REFERENCES "public"."explorations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_progress" ADD CONSTRAINT "user_progress_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "weekly_reviews" ADD CONSTRAINT "weekly_reviews_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "xp_events" ADD CONSTRAINT "xp_events_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "daily_adventures_user_date_idx" ON "daily_adventures" USING btree ("user_id","date");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "reflections_user_date_idx" ON "reflections" USING btree ("user_id","date");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "settings_user_key_idx" ON "settings" USING btree ("user_id","key");
|
||||
71
packages/db/drizzle/0001_extension.sql
Executable file
71
packages/db/drizzle/0001_extension.sql
Executable file
@@ -0,0 +1,71 @@
|
||||
CREATE TABLE IF NOT EXISTS "action_events" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"action_type" varchar(50) NOT NULL,
|
||||
"entity_type" varchar(50) NOT NULL,
|
||||
"entity_id" uuid NOT NULL,
|
||||
"summary" varchar(200) NOT NULL,
|
||||
"before_state" jsonb NOT NULL,
|
||||
"after_state" jsonb NOT NULL,
|
||||
"inverse_patch" jsonb,
|
||||
"metadata" jsonb DEFAULT '{}'::jsonb,
|
||||
"undoable" boolean DEFAULT true NOT NULL,
|
||||
"undone_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "action_events" ADD CONSTRAINT "action_events_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "action_events_user_created_idx" ON "action_events" ("user_id", "created_at" DESC);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "adventure_items" ADD COLUMN IF NOT EXISTS "deleted_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "books" ADD COLUMN IF NOT EXISTS "deleted_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "explorations" ADD COLUMN IF NOT EXISTS "deleted_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_prompt_templates" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"key" varchar(50) NOT NULL,
|
||||
"name" varchar(100) NOT NULL,
|
||||
"description" text DEFAULT '' NOT NULL,
|
||||
"category" varchar(30) DEFAULT 'template' NOT NULL,
|
||||
"body" text NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"version" integer DEFAULT 1 NOT NULL,
|
||||
"is_default_override" boolean DEFAULT false NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_prompt_templates" ADD CONSTRAINT "ai_prompt_templates_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "ai_prompt_templates_user_key_idx" ON "ai_prompt_templates" ("user_id", "key");
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_prompt_versions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"template_id" uuid NOT NULL,
|
||||
"version" integer NOT NULL,
|
||||
"body" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"created_by" varchar(20) DEFAULT 'user' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_prompt_versions" ADD CONSTRAINT "ai_prompt_versions_template_id_ai_prompt_templates_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."ai_prompt_templates"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_health_log" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"provider_type" varchar(30) NOT NULL,
|
||||
"status" varchar(20) NOT NULL,
|
||||
"latency_ms" integer,
|
||||
"model" varchar(100),
|
||||
"context_length" integer,
|
||||
"memory_usage_mb" integer,
|
||||
"base_url_safe" varchar(200),
|
||||
"error_message" text,
|
||||
"checked_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_health_log" ADD CONSTRAINT "ai_health_log_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
62
packages/db/drizzle/0002_feature_repair.sql
Executable file
62
packages/db/drizzle/0002_feature_repair.sql
Executable file
@@ -0,0 +1,62 @@
|
||||
CREATE TABLE IF NOT EXISTS "daily_todos" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"daily_adventure_id" uuid NOT NULL,
|
||||
"label" varchar(300) NOT NULL,
|
||||
"done" boolean DEFAULT false NOT NULL,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"deleted_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_todos" ADD CONSTRAINT "daily_todos_daily_adventure_id_daily_adventures_id_fk" FOREIGN KEY ("daily_adventure_id") REFERENCES "public"."daily_adventures"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD COLUMN IF NOT EXISTS "work_hours_target" numeric(4, 1);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD COLUMN IF NOT EXISTS "is_customized" boolean DEFAULT false NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD COLUMN IF NOT EXISTS "customized_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventure_items" ADD COLUMN IF NOT EXISTS "is_custom" boolean DEFAULT false NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventure_items" ADD COLUMN IF NOT EXISTS "enabled" boolean DEFAULT true NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventure_items" ADD COLUMN IF NOT EXISTS "deleted_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "adventure_templates" ADD COLUMN IF NOT EXISTS "sort_priority" integer DEFAULT 0 NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "adventure_templates" ADD COLUMN IF NOT EXISTS "deleted_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "adventure_templates" ADD COLUMN IF NOT EXISTS "is_system" boolean DEFAULT false NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "adventure_items" ADD COLUMN IF NOT EXISTS "enabled" boolean DEFAULT true NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "adventure_items" ADD COLUMN IF NOT EXISTS "item_kind" varchar(20) DEFAULT 'recurring' NOT NULL;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "reading_progress" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"calibre_book_id" integer NOT NULL,
|
||||
"calibre_uuid" varchar(36),
|
||||
"current_page" integer DEFAULT 0 NOT NULL,
|
||||
"total_pages" integer DEFAULT 300 NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'reading' NOT NULL,
|
||||
"last_read_date" date,
|
||||
"notes" text,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "reading_progress" ADD CONSTRAINT "reading_progress_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "reading_progress_user_calibre_idx" ON "reading_progress" ("user_id", "calibre_book_id");
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "reading_logs" ADD COLUMN IF NOT EXISTS "calibre_book_id" integer;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "reading_logs" ALTER COLUMN "book_id" DROP NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "teacher_content" ADD COLUMN IF NOT EXISTS "status" varchar(20) DEFAULT 'active' NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "teacher_content" ADD COLUMN IF NOT EXISTS "completed_note" text;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "teacher_content" ADD COLUMN IF NOT EXISTS "completed_at" timestamp with time zone;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "explorations" ADD COLUMN IF NOT EXISTS "minutes" integer;
|
||||
83
packages/db/drizzle/0003_ai_memory_and_day_modes.sql
Normal file
83
packages/db/drizzle/0003_ai_memory_and_day_modes.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
CREATE TABLE IF NOT EXISTS "ai_memories" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"category" varchar(50) NOT NULL,
|
||||
"title" varchar(200) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"source_type" varchar(30) NOT NULL DEFAULT 'manual',
|
||||
"source_ref" jsonb,
|
||||
"confidence" numeric(3, 2) DEFAULT 1 NOT NULL,
|
||||
"user_verified" boolean DEFAULT false NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"sensitivity" varchar(20) DEFAULT 'normal' NOT NULL,
|
||||
"tags" text[] DEFAULT '{}',
|
||||
"last_used_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,
|
||||
"archived_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_memories" ADD CONSTRAINT "ai_memories_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "ai_memories_user_category_idx" ON "ai_memories" ("user_id", "category");
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "ai_memories_user_enabled_idx" ON "ai_memories" ("user_id", "enabled");
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_memory_suggestions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"category" varchar(50) NOT NULL,
|
||||
"title" varchar(200) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"source_type" varchar(30) NOT NULL,
|
||||
"source_ref" jsonb,
|
||||
"confidence" numeric(3, 2) DEFAULT 0.7 NOT NULL,
|
||||
"status" varchar(20) DEFAULT 'pending' NOT NULL,
|
||||
"reviewed_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_memory_suggestions" ADD CONSTRAINT "ai_memory_suggestions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX IF NOT EXISTS "ai_memory_suggestions_user_status_idx" ON "ai_memory_suggestions" ("user_id", "status");
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_chat_sessions" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"title" varchar(200) DEFAULT 'New conversation' NOT NULL,
|
||||
"feature_context" jsonb DEFAULT '{}',
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"archived_at" timestamp with time zone
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_chat_sessions" ADD CONSTRAINT "ai_chat_sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_chat_messages" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"session_id" uuid NOT NULL,
|
||||
"role" varchar(20) NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}',
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_chat_messages" ADD CONSTRAINT "ai_chat_messages_session_id_ai_chat_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."ai_chat_sessions"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "ai_context_logs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" uuid NOT NULL,
|
||||
"feature" varchar(50) NOT NULL,
|
||||
"memory_ids" uuid[] DEFAULT '{}',
|
||||
"token_estimate" integer,
|
||||
"layers" jsonb DEFAULT '{}',
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "ai_context_logs" ADD CONSTRAINT "ai_context_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD COLUMN IF NOT EXISTS "day_mode" varchar(30) DEFAULT 'normal' NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD COLUMN IF NOT EXISTS "is_backfilled" boolean DEFAULT false NOT NULL;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "daily_adventures" ADD COLUMN IF NOT EXISTS "logged_at" timestamp with time zone;
|
||||
25
packages/db/package.json
Executable file
25
packages/db/package.json
Executable file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@adventureos/db",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./schema": "./src/schema.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "drizzle-kit generate",
|
||||
"migrate": "tsx src/migrate.ts",
|
||||
"seed": "tsx src/seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@adventureos/shared": "*",
|
||||
"drizzle-orm": "^0.39.3",
|
||||
"postgres": "^3.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"drizzle-kit": "^0.30.4",
|
||||
"tsx": "^4.19.3"
|
||||
}
|
||||
}
|
||||
12
packages/db/src/index.ts
Executable file
12
packages/db/src/index.ts
Executable file
@@ -0,0 +1,12 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const connectionString =
|
||||
process.env.DATABASE_URL ??
|
||||
"postgresql://adventureos:adventureos@localhost:5432/adventureos";
|
||||
|
||||
const client = postgres(connectionString, { max: 10 });
|
||||
export const db = drizzle(client, { schema });
|
||||
|
||||
export * from "./schema";
|
||||
24
packages/db/src/migrate.ts
Executable file
24
packages/db/src/migrate.ts
Executable file
@@ -0,0 +1,24 @@
|
||||
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import path from "path";
|
||||
|
||||
async function main() {
|
||||
const connectionString =
|
||||
process.env.DATABASE_URL ??
|
||||
"postgresql://adventureos:adventureos@localhost:5432/adventureos";
|
||||
|
||||
const client = postgres(connectionString, { max: 1 });
|
||||
const db = drizzle(client);
|
||||
const migrationsFolder = path.join(__dirname, "..", "drizzle");
|
||||
|
||||
console.log("Running migrations...");
|
||||
await migrate(db, { migrationsFolder });
|
||||
console.log("Migrations complete.");
|
||||
await client.end();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
481
packages/db/src/schema.ts
Executable file
481
packages/db/src/schema.ts
Executable file
@@ -0,0 +1,481 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
boolean,
|
||||
date,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
index,
|
||||
uuid,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
displayName: varchar("display_name", { length: 100 }).notNull().default("Traveler"),
|
||||
portraitConfig: jsonb("portrait_config")
|
||||
.$type<{ skinTone: string; hairColor: string; clothingColor: string }>()
|
||||
.notNull()
|
||||
.default({ skinTone: "#D4A574", hairColor: "#3D2314", clothingColor: "#3A6EA5" }),
|
||||
currentTitle: varchar("current_title", { length: 100 }),
|
||||
restDaysUsedWeek: integer("rest_days_used_week").notNull().default(0),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const userProgress = pgTable("user_progress", {
|
||||
userId: uuid("user_id")
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
totalXp: integer("total_xp").notNull().default(0),
|
||||
level: integer("level").notNull().default(1),
|
||||
currentChapter: varchar("current_chapter", { length: 100 }).notNull().default("Prologue: Awakening"),
|
||||
graceDaysRemaining: integer("grace_days_remaining").notNull().default(2),
|
||||
consistencyScore: integer("consistency_score").notNull().default(0),
|
||||
disciplineScore: integer("discipline_score").notNull().default(0),
|
||||
learningScore: integer("learning_score").notNull().default(0),
|
||||
spiritualScore: integer("spiritual_score").notNull().default(0),
|
||||
healthScore: integer("health_score").notNull().default(0),
|
||||
readingScore: integer("reading_score").notNull().default(0),
|
||||
scoresUpdatedAt: timestamp("scores_updated_at", { withTimezone: true }),
|
||||
lastVisitDate: date("last_visit_date"),
|
||||
});
|
||||
|
||||
export const adventureTemplates = pgTable("adventure_templates", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
name: varchar("name", { length: 100 }).notNull(),
|
||||
daysOfWeek: integer("days_of_week").array().notNull(),
|
||||
isDefault: boolean("is_default").notNull().default(false),
|
||||
sortPriority: integer("sort_priority").notNull().default(0),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
isSystem: boolean("is_system").notNull().default(false),
|
||||
});
|
||||
|
||||
export const adventureItems = pgTable("adventure_items", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
templateId: uuid("template_id")
|
||||
.notNull()
|
||||
.references(() => adventureTemplates.id, { onDelete: "cascade" }),
|
||||
type: varchar("type", { length: 20 }).notNull(),
|
||||
label: varchar("label", { length: 200 }).notNull(),
|
||||
config: jsonb("config").$type<Record<string, unknown>>().notNull().default({}),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
schedule: jsonb("schedule").$type<Record<string, unknown>>(),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
itemKind: varchar("item_kind", { length: 20 }).notNull().default("recurring"),
|
||||
});
|
||||
|
||||
export const dailyAdventures = pgTable(
|
||||
"daily_adventures",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
date: date("date").notNull(),
|
||||
templateId: uuid("template_id").references(() => adventureTemplates.id),
|
||||
isRestDay: boolean("is_rest_day").notNull().default(false),
|
||||
workHoursTarget: numeric("work_hours_target", { precision: 4, scale: 1 }),
|
||||
isCustomized: boolean("is_customized").notNull().default(false),
|
||||
customizedAt: timestamp("customized_at", { withTimezone: true }),
|
||||
dayMode: varchar("day_mode", { length: 30 }).notNull().default("normal"),
|
||||
isBackfilled: boolean("is_backfilled").notNull().default(false),
|
||||
loggedAt: timestamp("logged_at", { withTimezone: true }),
|
||||
},
|
||||
(t) => [uniqueIndex("daily_adventures_user_date_idx").on(t.userId, t.date)]
|
||||
);
|
||||
|
||||
export const dailyAdventureItems = pgTable("daily_adventure_items", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
dailyAdventureId: uuid("daily_adventure_id")
|
||||
.notNull()
|
||||
.references(() => dailyAdventures.id, { onDelete: "cascade" }),
|
||||
sourceItemId: uuid("source_item_id"),
|
||||
type: varchar("type", { length: 20 }).notNull(),
|
||||
label: varchar("label", { length: 200 }).notNull(),
|
||||
state: varchar("state", { length: 20 }).notNull().default("blank"),
|
||||
value: jsonb("value").$type<Record<string, unknown>>().notNull().default({}),
|
||||
config: jsonb("config").$type<Record<string, unknown>>().notNull().default({}),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
completedAt: timestamp("completed_at", { withTimezone: true }),
|
||||
isCustom: boolean("is_custom").notNull().default(false),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
});
|
||||
|
||||
export const dailyTodos = pgTable("daily_todos", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
dailyAdventureId: uuid("daily_adventure_id")
|
||||
.notNull()
|
||||
.references(() => dailyAdventures.id, { onDelete: "cascade" }),
|
||||
label: varchar("label", { length: 300 }).notNull(),
|
||||
done: boolean("done").notNull().default(false),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const books = pgTable("books", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: varchar("title", { length: 300 }).notNull(),
|
||||
author: varchar("author", { length: 200 }),
|
||||
totalPages: integer("total_pages").notNull().default(300),
|
||||
currentPage: integer("current_page").notNull().default(0),
|
||||
status: varchar("status", { length: 20 }).notNull().default("reading"),
|
||||
startedAt: timestamp("started_at", { withTimezone: true }).defaultNow(),
|
||||
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
||||
notes: text("notes"),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
});
|
||||
|
||||
export const readingLogs = pgTable("reading_logs", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
bookId: uuid("book_id").references(() => books.id, { onDelete: "cascade" }),
|
||||
date: date("date").notNull(),
|
||||
pagesRead: integer("pages_read").notNull().default(0),
|
||||
note: text("note"),
|
||||
calibreBookId: integer("calibre_book_id"),
|
||||
});
|
||||
|
||||
export const readingProgress = pgTable(
|
||||
"reading_progress",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
calibreBookId: integer("calibre_book_id").notNull(),
|
||||
calibreUuid: varchar("calibre_uuid", { length: 36 }),
|
||||
currentPage: integer("current_page").notNull().default(0),
|
||||
totalPages: integer("total_pages").notNull().default(300),
|
||||
status: varchar("status", { length: 20 }).notNull().default("reading"),
|
||||
lastReadDate: date("last_read_date"),
|
||||
notes: text("notes"),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [uniqueIndex("reading_progress_user_calibre_idx").on(t.userId, t.calibreBookId)]
|
||||
);
|
||||
|
||||
export const reflections = pgTable(
|
||||
"reflections",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
date: date("date").notNull(),
|
||||
wentWell: text("went_well").notNull().default(""),
|
||||
learned: text("learned").notNull().default(""),
|
||||
improveTomorrow: text("improve_tomorrow").notNull().default(""),
|
||||
},
|
||||
(t) => [uniqueIndex("reflections_user_date_idx").on(t.userId, t.date)]
|
||||
);
|
||||
|
||||
export const spiritualConfig = pgTable("spiritual_config", {
|
||||
userId: uuid("user_id")
|
||||
.primaryKey()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
prayerLabels: text("prayer_labels").array().notNull(),
|
||||
litanyLabels: text("litany_labels").array().notNull(),
|
||||
});
|
||||
|
||||
export const explorations = pgTable("explorations", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: varchar("title", { length: 300 }).notNull(),
|
||||
description: text("description").notNull(),
|
||||
category: varchar("category", { length: 50 }).notNull(),
|
||||
status: varchar("status", { length: 20 }).notNull().default("suggested"),
|
||||
weekOf: date("week_of").notNull(),
|
||||
completedNote: text("completed_note"),
|
||||
acceptedAt: timestamp("accepted_at", { withTimezone: true }),
|
||||
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
||||
minutes: integer("minutes"),
|
||||
});
|
||||
|
||||
export const achievements = pgTable("achievements", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
key: varchar("key", { length: 100 }).notNull(),
|
||||
unlockedAt: timestamp("unlocked_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
||||
});
|
||||
|
||||
export const aiSuggestions = pgTable("ai_suggestions", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
role: varchar("role", { length: 20 }).notNull(),
|
||||
content: jsonb("content").$type<Record<string, unknown>>().notNull(),
|
||||
generatedAt: timestamp("generated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
dismissed: boolean("dismissed").notNull().default(false),
|
||||
});
|
||||
|
||||
export const weeklyReviews = pgTable("weekly_reviews", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
weekStart: date("week_start").notNull(),
|
||||
content: jsonb("content").$type<Record<string, unknown>>().notNull().default({}),
|
||||
mentorLetter: text("mentor_letter"),
|
||||
xpEarned: integer("xp_earned").notNull().default(0),
|
||||
userIntention: text("user_intention"),
|
||||
});
|
||||
|
||||
export const xpEvents = pgTable("xp_events", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
date: date("date").notNull(),
|
||||
source: varchar("source", { length: 50 }).notNull(),
|
||||
amount: integer("amount").notNull(),
|
||||
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
||||
});
|
||||
|
||||
export const settings = pgTable(
|
||||
"settings",
|
||||
{
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
key: varchar("key", { length: 100 }).notNull(),
|
||||
value: jsonb("value").$type<unknown>().notNull(),
|
||||
},
|
||||
(t) => [uniqueIndex("settings_user_key_idx").on(t.userId, t.key)]
|
||||
);
|
||||
|
||||
export const teacherContent = pgTable("teacher_content", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
explorationId: uuid("exploration_id").references(() => explorations.id),
|
||||
topic: varchar("topic", { length: 300 }).notNull(),
|
||||
content: jsonb("content").$type<Record<string, unknown>>().notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
status: varchar("status", { length: 20 }).notNull().default("active"),
|
||||
completedNote: text("completed_note"),
|
||||
completedAt: timestamp("completed_at", { withTimezone: true }),
|
||||
});
|
||||
|
||||
export const actionEvents = pgTable("action_events", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
actionType: varchar("action_type", { length: 50 }).notNull(),
|
||||
entityType: varchar("entity_type", { length: 50 }).notNull(),
|
||||
entityId: uuid("entity_id").notNull(),
|
||||
summary: varchar("summary", { length: 200 }).notNull(),
|
||||
beforeState: jsonb("before_state").$type<Record<string, unknown>>().notNull(),
|
||||
afterState: jsonb("after_state").$type<Record<string, unknown>>().notNull(),
|
||||
inversePatch: jsonb("inverse_patch").$type<Record<string, unknown>>(),
|
||||
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
||||
undoable: boolean("undoable").notNull().default(true),
|
||||
undoneAt: timestamp("undone_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const aiPromptTemplates = pgTable(
|
||||
"ai_prompt_templates",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
key: varchar("key", { length: 50 }).notNull(),
|
||||
name: varchar("name", { length: 100 }).notNull(),
|
||||
description: text("description").notNull().default(""),
|
||||
category: varchar("category", { length: 30 }).notNull().default("template"),
|
||||
body: text("body").notNull(),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
version: integer("version").notNull().default(1),
|
||||
isDefaultOverride: boolean("is_default_override").notNull().default(false),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [uniqueIndex("ai_prompt_templates_user_key_idx").on(t.userId, t.key)]
|
||||
);
|
||||
|
||||
export const aiPromptVersions = pgTable("ai_prompt_versions", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
templateId: uuid("template_id")
|
||||
.notNull()
|
||||
.references(() => aiPromptTemplates.id, { onDelete: "cascade" }),
|
||||
version: integer("version").notNull(),
|
||||
body: text("body").notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
createdBy: varchar("created_by", { length: 20 }).notNull().default("user"),
|
||||
});
|
||||
|
||||
export const aiMemories = pgTable(
|
||||
"ai_memories",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
category: varchar("category", { length: 50 }).notNull(),
|
||||
title: varchar("title", { length: 200 }).notNull(),
|
||||
content: text("content").notNull(),
|
||||
sourceType: varchar("source_type", { length: 30 }).notNull().default("manual"),
|
||||
sourceRef: jsonb("source_ref").$type<{ type: string; id?: string; date?: string }>(),
|
||||
confidence: numeric("confidence", { precision: 3, scale: 2 }).notNull().default("1"),
|
||||
userVerified: boolean("user_verified").notNull().default(false),
|
||||
enabled: boolean("enabled").notNull().default(true),
|
||||
sensitivity: varchar("sensitivity", { length: 20 }).notNull().default("normal"),
|
||||
tags: text("tags").array().notNull().default([]),
|
||||
lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
||||
},
|
||||
(t) => [
|
||||
index("ai_memories_user_category_idx").on(t.userId, t.category),
|
||||
index("ai_memories_user_enabled_idx").on(t.userId, t.enabled),
|
||||
]
|
||||
);
|
||||
|
||||
export const aiMemorySuggestions = pgTable("ai_memory_suggestions", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
category: varchar("category", { length: 50 }).notNull(),
|
||||
title: varchar("title", { length: 200 }).notNull(),
|
||||
content: text("content").notNull(),
|
||||
sourceType: varchar("source_type", { length: 30 }).notNull(),
|
||||
sourceRef: jsonb("source_ref").$type<{ type: string; id?: string; date?: string }>(),
|
||||
confidence: numeric("confidence", { precision: 3, scale: 2 }).notNull().default("0.7"),
|
||||
status: varchar("status", { length: 20 }).notNull().default("pending"),
|
||||
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const aiChatSessions = pgTable("ai_chat_sessions", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: varchar("title", { length: 200 }).notNull().default("New conversation"),
|
||||
featureContext: jsonb("feature_context").$type<Record<string, unknown>>().default({}),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
||||
});
|
||||
|
||||
export const aiChatMessages = pgTable("ai_chat_messages", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
sessionId: uuid("session_id")
|
||||
.notNull()
|
||||
.references(() => aiChatSessions.id, { onDelete: "cascade" }),
|
||||
role: varchar("role", { length: 20 }).notNull(),
|
||||
content: text("content").notNull(),
|
||||
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const aiContextLogs = pgTable("ai_context_logs", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
feature: varchar("feature", { length: 50 }).notNull(),
|
||||
memoryIds: uuid("memory_ids").array().notNull().default([]),
|
||||
tokenEstimate: integer("token_estimate"),
|
||||
layers: jsonb("layers").$type<Record<string, unknown>>().default({}),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const aiHealthLog = pgTable("ai_health_log", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
providerType: varchar("provider_type", { length: 30 }).notNull(),
|
||||
status: varchar("status", { length: 20 }).notNull(),
|
||||
latencyMs: integer("latency_ms"),
|
||||
model: varchar("model", { length: 100 }),
|
||||
contextLength: integer("context_length"),
|
||||
memoryUsageMb: integer("memory_usage_mb"),
|
||||
baseUrlSafe: varchar("base_url_safe", { length: 200 }),
|
||||
errorMessage: text("error_message"),
|
||||
checkedAt: timestamp("checked_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const usersRelations = relations(users, ({ one, many }) => ({
|
||||
progress: one(userProgress),
|
||||
templates: many(adventureTemplates),
|
||||
dailyAdventures: many(dailyAdventures),
|
||||
books: many(books),
|
||||
reflections: many(reflections),
|
||||
spiritualConfig: one(spiritualConfig),
|
||||
explorations: many(explorations),
|
||||
achievements: many(achievements),
|
||||
aiSuggestions: many(aiSuggestions),
|
||||
weeklyReviews: many(weeklyReviews),
|
||||
xpEvents: many(xpEvents),
|
||||
settings: many(settings),
|
||||
actionEvents: many(actionEvents),
|
||||
aiPromptTemplates: many(aiPromptTemplates),
|
||||
aiHealthLogs: many(aiHealthLog),
|
||||
aiMemories: many(aiMemories),
|
||||
aiMemorySuggestions: many(aiMemorySuggestions),
|
||||
aiChatSessions: many(aiChatSessions),
|
||||
}));
|
||||
|
||||
export const adventureTemplatesRelations = relations(
|
||||
adventureTemplates,
|
||||
({ one, many }) => ({
|
||||
user: one(users, { fields: [adventureTemplates.userId], references: [users.id] }),
|
||||
items: many(adventureItems),
|
||||
})
|
||||
);
|
||||
|
||||
export const adventureItemsRelations = relations(adventureItems, ({ one }) => ({
|
||||
template: one(adventureTemplates, {
|
||||
fields: [adventureItems.templateId],
|
||||
references: [adventureTemplates.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const dailyAdventuresRelations = relations(
|
||||
dailyAdventures,
|
||||
({ one, many }) => ({
|
||||
user: one(users, { fields: [dailyAdventures.userId], references: [users.id] }),
|
||||
items: many(dailyAdventureItems),
|
||||
todos: many(dailyTodos),
|
||||
})
|
||||
);
|
||||
|
||||
export const dailyAdventureItemsRelations = relations(
|
||||
dailyAdventureItems,
|
||||
({ one }) => ({
|
||||
dailyAdventure: one(dailyAdventures, {
|
||||
fields: [dailyAdventureItems.dailyAdventureId],
|
||||
references: [dailyAdventures.id],
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
export const booksRelations = relations(books, ({ one, many }) => ({
|
||||
user: one(users, { fields: [books.userId], references: [users.id] }),
|
||||
readingLogs: many(readingLogs),
|
||||
}));
|
||||
148
packages/db/src/seed.ts
Executable file
148
packages/db/src/seed.ts
Executable file
@@ -0,0 +1,148 @@
|
||||
import { db } from "./index";
|
||||
import {
|
||||
adventureItems,
|
||||
adventureTemplates,
|
||||
spiritualConfig,
|
||||
userProgress,
|
||||
users,
|
||||
settings,
|
||||
} from "./schema";
|
||||
|
||||
const DEFAULT_PRAYER = [
|
||||
"Morning",
|
||||
"Midday",
|
||||
"Evening",
|
||||
"Rosary",
|
||||
"Examen",
|
||||
];
|
||||
|
||||
const DEFAULT_LITANIES = [
|
||||
"Litany 1",
|
||||
"Litany 2",
|
||||
"Litany 3",
|
||||
"Litany 4",
|
||||
"Litany 5",
|
||||
"Litany 6",
|
||||
];
|
||||
|
||||
async function seed() {
|
||||
const existing = await db.select().from(users).limit(1);
|
||||
if (existing.length > 0) {
|
||||
console.log("User already exists, skipping seed.");
|
||||
return;
|
||||
}
|
||||
|
||||
const [user] = await db
|
||||
.insert(users)
|
||||
.values({ displayName: "Traveler" })
|
||||
.returning();
|
||||
|
||||
await db.insert(userProgress).values({ userId: user.id });
|
||||
await db.insert(spiritualConfig).values({
|
||||
userId: user.id,
|
||||
prayerLabels: DEFAULT_PRAYER,
|
||||
litanyLabels: DEFAULT_LITANIES,
|
||||
});
|
||||
await db.insert(settings).values([
|
||||
{ userId: user.id, key: "weekly_reading_goal", value: 50 },
|
||||
{ userId: user.id, key: "notifications_enabled", value: false },
|
||||
{ userId: user.id, key: "sounds_enabled", value: false },
|
||||
{ userId: user.id, key: "theme", value: "minimal-dark" },
|
||||
]);
|
||||
|
||||
const templates = [
|
||||
{
|
||||
name: "Weekday",
|
||||
daysOfWeek: [1, 2, 3, 4, 5],
|
||||
isDefault: true,
|
||||
isSystem: true,
|
||||
sortPriority: 0,
|
||||
items: [
|
||||
{ type: "duration", label: "Work", config: { targetHours: 7.5 }, sortOrder: 0 },
|
||||
{ type: "checkbox", label: "Exercise", config: {}, sortOrder: 1 },
|
||||
{ type: "reading", label: "Read", config: {}, sortOrder: 2 },
|
||||
{ type: "timeblock", label: "Class 1", config: { scheduledTime: "18:00" }, sortOrder: 3 },
|
||||
{ type: "timeblock", label: "Class 2", config: { scheduledTime: "20:00" }, sortOrder: 4 },
|
||||
{ type: "checklist", label: "Prayer", config: { checklistSize: 5 }, sortOrder: 5 },
|
||||
{ type: "checklist", label: "Litanies", config: { checklistSize: 6 }, sortOrder: 6 },
|
||||
{ type: "note", label: "Notes", config: {}, sortOrder: 7 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Saturday",
|
||||
daysOfWeek: [6],
|
||||
isDefault: false,
|
||||
isSystem: true,
|
||||
sortPriority: 0,
|
||||
items: [
|
||||
{ type: "checkbox", label: "Exercise", config: {}, sortOrder: 0 },
|
||||
{ type: "reading", label: "Read", config: {}, sortOrder: 1 },
|
||||
{ type: "checklist", label: "Prayer", config: { checklistSize: 5 }, sortOrder: 2 },
|
||||
{ type: "checklist", label: "Litanies", config: { checklistSize: 6 }, sortOrder: 3 },
|
||||
{ type: "note", label: "Notes", config: {}, sortOrder: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sunday",
|
||||
daysOfWeek: [0],
|
||||
isDefault: false,
|
||||
isSystem: true,
|
||||
sortPriority: 0,
|
||||
items: [
|
||||
{ type: "reading", label: "Read", config: {}, sortOrder: 0 },
|
||||
{ type: "checkbox", label: "Exercise", config: {}, sortOrder: 1 },
|
||||
{ type: "checklist", label: "Prayer", config: { checklistSize: 5 }, sortOrder: 2 },
|
||||
{ type: "checklist", label: "Litanies", config: { checklistSize: 6 }, sortOrder: 3 },
|
||||
{ type: "note", label: "Notes", config: {}, sortOrder: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Teaching Day",
|
||||
daysOfWeek: [2, 4],
|
||||
isDefault: false,
|
||||
isSystem: true,
|
||||
sortPriority: 10,
|
||||
items: [
|
||||
{ type: "duration", label: "Work", config: { targetHours: 7.5 }, sortOrder: 0 },
|
||||
{ type: "checkbox", label: "Teaching", config: {}, sortOrder: 1 },
|
||||
{ type: "checkbox", label: "Exercise", config: {}, sortOrder: 2 },
|
||||
{ type: "reading", label: "Read", config: {}, sortOrder: 3 },
|
||||
{ type: "checklist", label: "Prayer", config: { checklistSize: 5 }, sortOrder: 4 },
|
||||
{ type: "checklist", label: "Litanies", config: { checklistSize: 6 }, sortOrder: 5 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
for (const tpl of templates) {
|
||||
const [template] = await db
|
||||
.insert(adventureTemplates)
|
||||
.values({
|
||||
userId: user.id,
|
||||
name: tpl.name,
|
||||
daysOfWeek: tpl.daysOfWeek,
|
||||
isDefault: tpl.isDefault,
|
||||
isSystem: tpl.isSystem,
|
||||
sortPriority: tpl.sortPriority,
|
||||
})
|
||||
.returning();
|
||||
|
||||
for (const item of tpl.items) {
|
||||
await db.insert(adventureItems).values({
|
||||
templateId: template.id,
|
||||
type: item.type,
|
||||
label: item.label,
|
||||
config: item.config,
|
||||
sortOrder: item.sortOrder,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Seed complete. User ID:", user.id);
|
||||
}
|
||||
|
||||
seed()
|
||||
.then(() => process.exit(0))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user