import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core"; export const users = sqliteTable("users", { id: text("id").primaryKey(), discordId: text("discord_id").notNull().unique(), username: text("username").notNull(), globalName: text("global_name"), avatarHash: text("avatar_hash"), timezone: text("timezone").notNull().default("UTC"), createdAt: integer("created_at").notNull(), updatedAt: integer("updated_at").notNull(), }); export const sessions = sqliteTable("sessions", { tokenHash: text("token_hash").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), createdAt: integer("created_at").notNull(), expiresAt: integer("expires_at").notNull(), }, table => [ index("sessions_user_id_idx").on(table.userId), index("sessions_expires_at_idx").on(table.expiresAt), ]); export const discordDeliveries = sqliteTable("discord_deliveries", { id: text("id").primaryKey(), userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }), imageHash: text("image_hash").notNull(), status: text("status").notNull(), messageUrl: text("message_url"), createdAt: integer("created_at").notNull(), }); // Revisions are append-only, including multiple edits on the same local date. export const habits = sqliteTable('habits', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id), createdDate: text('created_date').notNull(), materializedThrough: text('materialized_through'), createdAt: integer('created_at').notNull(), }, t => [index('habits_user_idx').on(t.userId)]); export const habitRevisions = sqliteTable('habit_revisions', { id: integer('id').primaryKey({ autoIncrement: true }), habitId: text('habit_id').notNull().references(() => habits.id), effectiveDate: text('effective_date').notNull(), config: text('config', { mode: 'json' }).$type().notNull(), createdAt: integer('created_at').notNull(), }, t => [index('habit_revisions_date_idx').on(t.habitId, t.effectiveDate, t.id)]); export const habitDays = sqliteTable('habit_days', { id: integer('id').primaryKey({ autoIncrement: true }), habitId: text('habit_id').notNull().references(() => habits.id), revisionId: integer('revision_id').notNull().references(() => habitRevisions.id), date: text('date').notNull(), timezone: text('timezone').notNull(), endsAt: integer('ends_at').notNull(), countSet: integer('count_set', { mode: 'boolean' }).notNull().default(false), count: integer('count').notNull().default(0), done: integer('done', { mode: 'boolean' }).notNull().default(false), }, t => [index('habit_days_lookup_idx').on(t.habitId, t.date, t.revisionId)]); export const taskOccurrences = sqliteTable('task_occurrences', { id: text('id').primaryKey(), dayId: integer('day_id').notNull().references(() => habitDays.id), taskId: text('task_id').notNull(), name: text('name').notNull(), done: integer('done', { mode: 'boolean' }).notNull().default(false), expiredAt: integer('expired_at'), closedAt: integer('closed_at'), updatedAt: integer('updated_at').notNull(), }, t => [index('task_occurrences_day_idx').on(t.dayId)]); export const progressEvents = sqliteTable('progress_events', { id: integer('id').primaryKey({ autoIncrement: true }), dayId: integer('day_id').notNull().references(() => habitDays.id), occurrenceId: text('occurrence_id'), before: text('before', { mode: 'json' }).$type().notNull(), after: text('after', { mode: 'json' }).$type().notNull(), createdAt: integer('created_at').notNull(), }, t => [index('progress_events_day_idx').on(t.dayId)]); export const habitCalendarSettings = sqliteTable('habit_calendar_settings', { habitId: text('habit_id').primaryKey().references(() => habits.id), settings: text('settings', { mode: 'json' }).$type().notNull(), }); export const combinedCharts = sqliteTable('combined_charts', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id), name: text('name').notNull(), habitIds: text('habit_ids', { mode: 'json' }).$type().notNull(), settings: text('settings', { mode: 'json' }).$type().notNull(), createdAt: integer('created_at').notNull(), updatedAt: integer('updated_at').notNull(), }, t => [index('combined_charts_user_idx').on(t.userId)]); export const sharingLimits = sqliteTable('sharing_limits', { key: text('key').primaryKey(), startedAt: integer('started_at').notNull(), count: integer('count').notNull(), }); export const reminderSettings = sqliteTable('reminder_settings', { userId: text('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }), enabled: integer('enabled', { mode: 'boolean' }).notNull().default(false), time: text('time').notNull().default('20:00'), quietStart: text('quiet_start').notNull().default('22:00'), quietEnd: text('quiet_end').notNull().default('08:00'), updatedAt: integer('updated_at').notNull(), }); export const reminderDeliveries = sqliteTable('reminder_deliveries', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), date: text('date').notNull(), status: text('status').notNull(), retryAt: integer('retry_at'), updatedAt: integer('updated_at').notNull(), }, t => [uniqueIndex('reminder_deliveries_user_date_idx').on(t.userId, t.date)]); export const reminderWorkerState = sqliteTable('reminder_worker_state', { id: text('id').primaryKey(), blockedUntil: integer('blocked_until').notNull(), }); export const liveDiscordCards = sqliteTable('live_discord_cards', { userId: text('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }), channelId: text('channel_id').notNull(), messageId: text('message_id'), messageUrl: text('message_url'), nonce: text('nonce').notNull(), theme: text('theme').$type().notNull(), status: text('status').$type<'active' | 'paused' | 'sending' | 'uncertain' | 'error'>().notNull(), imageHash: text('image_hash'), dirty: integer('dirty', { mode: 'boolean' }).notNull().default(true), error: text('error'), retryAt: integer('retry_at'), updatedAt: integer('updated_at').notNull(), });