initial
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-06-26 09:21:14 +01:00
commit 194330fb47
40 changed files with 11873 additions and 0 deletions

12
packages/db/drizzle.config.ts Executable file
View 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",
},
});

View 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");

View 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;

View 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;

View 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
View 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
View 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
View 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
View 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
View 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);
});