diff --git a/src/db/schema.ts b/src/db/schema.ts index c94ee98..5b24960 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -7,9 +7,164 @@ * - Plan: Group of related tasks within phase * - Task: Individual work item * - * Schema will be defined in Task 3. + * Plus a task_dependencies table for task dependency relationships. */ -// Placeholder export to allow index.ts to compile -// Full schema defined in Task 3 -export {}; +import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'; +import { relations, type InferInsertModel, type InferSelectModel } from 'drizzle-orm'; + +// ============================================================================ +// INITIATIVES +// ============================================================================ + +export const initiatives = sqliteTable('initiatives', { + id: text('id').primaryKey(), + name: text('name').notNull(), + description: text('description'), + status: text('status', { enum: ['active', 'completed', 'archived'] }) + .notNull() + .default('active'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(), +}); + +export const initiativesRelations = relations(initiatives, ({ many }) => ({ + phases: many(phases), +})); + +export type Initiative = InferSelectModel; +export type NewInitiative = InferInsertModel; + +// ============================================================================ +// PHASES +// ============================================================================ + +export const phases = sqliteTable('phases', { + id: text('id').primaryKey(), + initiativeId: text('initiative_id') + .notNull() + .references(() => initiatives.id, { onDelete: 'cascade' }), + number: integer('number').notNull(), + name: text('name').notNull(), + description: text('description'), + status: text('status', { enum: ['pending', 'in_progress', 'completed'] }) + .notNull() + .default('pending'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(), +}); + +export const phasesRelations = relations(phases, ({ one, many }) => ({ + initiative: one(initiatives, { + fields: [phases.initiativeId], + references: [initiatives.id], + }), + plans: many(plans), +})); + +export type Phase = InferSelectModel; +export type NewPhase = InferInsertModel; + +// ============================================================================ +// PLANS +// ============================================================================ + +export const plans = sqliteTable('plans', { + id: text('id').primaryKey(), + phaseId: text('phase_id') + .notNull() + .references(() => phases.id, { onDelete: 'cascade' }), + number: integer('number').notNull(), + name: text('name').notNull(), + description: text('description'), + status: text('status', { enum: ['pending', 'in_progress', 'completed'] }) + .notNull() + .default('pending'), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(), +}); + +export const plansRelations = relations(plans, ({ one, many }) => ({ + phase: one(phases, { + fields: [plans.phaseId], + references: [phases.id], + }), + tasks: many(tasks), +})); + +export type Plan = InferSelectModel; +export type NewPlan = InferInsertModel; + +// ============================================================================ +// TASKS +// ============================================================================ + +export const tasks = sqliteTable('tasks', { + id: text('id').primaryKey(), + planId: text('plan_id') + .notNull() + .references(() => plans.id, { onDelete: 'cascade' }), + name: text('name').notNull(), + description: text('description'), + type: text('type', { + enum: ['auto', 'checkpoint:human-verify', 'checkpoint:decision', 'checkpoint:human-action'], + }) + .notNull() + .default('auto'), + priority: text('priority', { enum: ['low', 'medium', 'high'] }) + .notNull() + .default('medium'), + status: text('status', { + enum: ['pending_approval', 'pending', 'in_progress', 'completed', 'blocked'], + }) + .notNull() + .default('pending'), + order: integer('order').notNull().default(0), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(), +}); + +export const tasksRelations = relations(tasks, ({ one, many }) => ({ + plan: one(plans, { + fields: [tasks.planId], + references: [plans.id], + }), + // Dependencies: tasks this task depends on + dependsOn: many(taskDependencies, { relationName: 'dependentTask' }), + // Dependents: tasks that depend on this task + dependents: many(taskDependencies, { relationName: 'dependencyTask' }), +})); + +export type Task = InferSelectModel; +export type NewTask = InferInsertModel; + +// ============================================================================ +// TASK DEPENDENCIES +// ============================================================================ + +export const taskDependencies = sqliteTable('task_dependencies', { + id: text('id').primaryKey(), + taskId: text('task_id') + .notNull() + .references(() => tasks.id, { onDelete: 'cascade' }), + dependsOnTaskId: text('depends_on_task_id') + .notNull() + .references(() => tasks.id, { onDelete: 'cascade' }), + createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), +}); + +export const taskDependenciesRelations = relations(taskDependencies, ({ one }) => ({ + task: one(tasks, { + fields: [taskDependencies.taskId], + references: [tasks.id], + relationName: 'dependentTask', + }), + dependsOnTask: one(tasks, { + fields: [taskDependencies.dependsOnTaskId], + references: [tasks.id], + relationName: 'dependencyTask', + }), +})); + +export type TaskDependency = InferSelectModel; +export type NewTaskDependency = InferInsertModel;