import { pgTable, text, integer, bigint, boolean, unique, index, primaryKey, AnyPgColumn } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; export const ENTITIES = ['categories', 'tasks', 'subtasks', 'repeatProfiles', 'friendships'] as const; export type EntityName = (typeof ENTITIES)[number]; export const users = pgTable('users', { id: text('id').primaryKey(), username: text('username').notNull().unique(), passwordHash: text('password_hash').notNull(), resetToken: text('reset_token'), resetTokenExpiry: bigint('reset_token_expiry', { mode: 'number' }), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }); export const friendships = pgTable( 'friendships', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), friendId: text('friend_id').notNull().references(() => users.id, { onDelete: 'cascade' }), status: text('status', { enum: ['pending', 'accepted'] }).notNull().default('pending'), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }, (t) => ({ pairUnique: unique('friendships_pair').on(t.userId, t.friendId), userIdx: index('friendships_user_idx').on(t.userId), friendIdx: index('friendships_friend_idx').on(t.friendId), }) ); export const categories = pgTable('categories', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), name: text('name').notNull(), color: text('color').notNull(), order: integer('order').notNull().default(0), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }); export const tasks = pgTable('tasks', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), categoryId: text('category_id').references(() => categories.id, { onDelete: 'cascade' }), tags: text('tags').notNull().default(''), title: text('title').notNull(), description: text('description').notNull().default(''), priority: text('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'), completed: boolean('completed').notNull().default(false), dueDate: bigint('due_date', { mode: 'number' }).notNull().default(0), dueTime: text('due_time').notNull().default(''), endTime: text('end_time').notNull().default(''), allDay: boolean('all_day').notNull().default(false), repeat: text('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), repeatInterval: integer('repeat_interval').notNull().default(1), repeatDays: text('repeat_days').notNull().default(''), seriesId: text('series_id').notNull().default(''), assigneeId: text('assignee_id').references(() => users.id, { onDelete: 'set null' }), reminder: text('reminder', { enum: ['none', 'at_time', '15', '30', '60', '120', '1440'] }).notNull().default('none'), reminders: text('reminders').notNull().default(''), completedAt: bigint('completed_at', { mode: 'number' }), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }); export const tombstones = pgTable( 'tombstones', { entity: text('entity', { enum: ENTITIES }).notNull(), entityId: text('entity_id').notNull(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), updatedAt: bigint('updated_at', { mode: 'number' }).notNull(), }, (t) => ({ pk: primaryKey({ columns: [t.entity, t.entityId] }), userIdx: index('tombstones_user_updated_idx').on(t.userId, t.updatedAt), }) ); // Define subtasks with explicit type to avoid circular reference export const subtasks = pgTable('subtasks', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), taskId: text('task_id').notNull().references(() => tasks.id, { onDelete: 'cascade' }), parentSubtaskId: text('parent_subtask_id').references((): AnyPgColumn => subtasks.id, { onDelete: 'cascade' }), categoryId: text('category_id').references(() => categories.id, { onDelete: 'set null' }), title: text('title').notNull(), description: text('description').notNull().default(''), priority: text('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'), completed: boolean('completed').notNull().default(false), dueDate: bigint('due_date', { mode: 'number' }).notNull().default(0), dueTime: text('due_time').notNull().default(''), endTime: text('end_time').notNull().default(''), allDay: boolean('all_day').notNull().default(false), repeat: text('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), repeatInterval: integer('repeat_interval').notNull().default(1), repeatDays: text('repeat_days').notNull().default(''), seriesId: text('series_id').notNull().default(''), reminder: text('reminder', { enum: ['none', 'at_time', '15', '30', '60', '120', '1440'] }).notNull().default('none'), reminders: text('reminders').notNull().default(''), assigneeId: text('assignee_id').references(() => users.id, { onDelete: 'set null' }), order: integer('order').notNull().default(0), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }, (t) => ({ parentIdx: index('subtasks_parent_idx').on(t.parentSubtaskId), taskIdx: index('subtasks_task_idx').on(t.taskId), userIdx: index('subtasks_user_idx').on(t.userId), })); export const repeatProfiles = pgTable('repeat_profiles', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), name: text('name').notNull(), repeat: text('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), repeatInterval: integer('repeat_interval').notNull().default(1), repeatDays: text('repeat_days').notNull().default(''), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }); export const userSettings = pgTable('user_settings', { userId: text('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }), darkMode: boolean('dark_mode').notNull().default(false), notifications: boolean('notifications').notNull().default(true), reminderTime: text('reminder_time').notNull().default('09:00'), defaultCategory: text('default_category'), sortBy: text('sort_by', { enum: ['dueDate', 'priority', 'title', 'createdAt'] }).notNull().default('dueDate'), sortOrder: text('sort_order', { enum: ['asc', 'desc'] }).notNull().default('asc'), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), });