diff --git a/src/db/repositories/drizzle/index.ts b/src/db/repositories/drizzle/index.ts new file mode 100644 index 0000000..d906b48 --- /dev/null +++ b/src/db/repositories/drizzle/index.ts @@ -0,0 +1,11 @@ +/** + * Drizzle Repository Adapters + * + * Re-exports all Drizzle implementations of repository interfaces. + * These are the ADAPTERS for the repository PORTS. + */ + +export { DrizzleInitiativeRepository } from './initiative.js'; +export { DrizzlePhaseRepository } from './phase.js'; +export { DrizzlePlanRepository } from './plan.js'; +export { DrizzleTaskRepository } from './task.js'; diff --git a/src/db/repositories/drizzle/initiative.ts b/src/db/repositories/drizzle/initiative.ts new file mode 100644 index 0000000..69ed3e6 --- /dev/null +++ b/src/db/repositories/drizzle/initiative.ts @@ -0,0 +1,78 @@ +/** + * Drizzle Initiative Repository Adapter + * + * Implements InitiativeRepository interface using Drizzle ORM. + */ + +import { eq } from 'drizzle-orm'; +import { nanoid } from 'nanoid'; +import type { DrizzleDatabase } from '../../index.js'; +import { initiatives, type Initiative } from '../../schema.js'; +import type { + InitiativeRepository, + CreateInitiativeData, + UpdateInitiativeData, +} from '../initiative-repository.js'; + +/** + * Drizzle adapter for InitiativeRepository. + * + * Uses dependency injection for database instance, + * enabling isolated test databases. + */ +export class DrizzleInitiativeRepository implements InitiativeRepository { + constructor(private db: DrizzleDatabase) {} + + async create(data: CreateInitiativeData): Promise { + const now = new Date(); + const initiative = { + id: nanoid(), + ...data, + createdAt: now, + updatedAt: now, + }; + + await this.db.insert(initiatives).values(initiative); + + return initiative as Initiative; + } + + async findById(id: string): Promise { + const result = await this.db + .select() + .from(initiatives) + .where(eq(initiatives.id, id)) + .limit(1); + + return result[0] ?? null; + } + + async findAll(): Promise { + return this.db.select().from(initiatives); + } + + async update(id: string, data: UpdateInitiativeData): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Initiative not found: ${id}`); + } + + const updated = { + ...data, + updatedAt: new Date(), + }; + + await this.db.update(initiatives).set(updated).where(eq(initiatives.id, id)); + + return { ...existing, ...updated } as Initiative; + } + + async delete(id: string): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Initiative not found: ${id}`); + } + + await this.db.delete(initiatives).where(eq(initiatives.id, id)); + } +} diff --git a/src/db/repositories/drizzle/phase.ts b/src/db/repositories/drizzle/phase.ts new file mode 100644 index 0000000..fa17081 --- /dev/null +++ b/src/db/repositories/drizzle/phase.ts @@ -0,0 +1,82 @@ +/** + * Drizzle Phase Repository Adapter + * + * Implements PhaseRepository interface using Drizzle ORM. + */ + +import { eq, asc } from 'drizzle-orm'; +import { nanoid } from 'nanoid'; +import type { DrizzleDatabase } from '../../index.js'; +import { phases, type Phase } from '../../schema.js'; +import type { + PhaseRepository, + CreatePhaseData, + UpdatePhaseData, +} from '../phase-repository.js'; + +/** + * Drizzle adapter for PhaseRepository. + * + * Uses dependency injection for database instance, + * enabling isolated test databases. + */ +export class DrizzlePhaseRepository implements PhaseRepository { + constructor(private db: DrizzleDatabase) {} + + async create(data: CreatePhaseData): Promise { + const now = new Date(); + const phase = { + id: nanoid(), + ...data, + createdAt: now, + updatedAt: now, + }; + + await this.db.insert(phases).values(phase); + + return phase as Phase; + } + + async findById(id: string): Promise { + const result = await this.db + .select() + .from(phases) + .where(eq(phases.id, id)) + .limit(1); + + return result[0] ?? null; + } + + async findByInitiativeId(initiativeId: string): Promise { + return this.db + .select() + .from(phases) + .where(eq(phases.initiativeId, initiativeId)) + .orderBy(asc(phases.number)); + } + + async update(id: string, data: UpdatePhaseData): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Phase not found: ${id}`); + } + + const updated = { + ...data, + updatedAt: new Date(), + }; + + await this.db.update(phases).set(updated).where(eq(phases.id, id)); + + return { ...existing, ...updated } as Phase; + } + + async delete(id: string): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Phase not found: ${id}`); + } + + await this.db.delete(phases).where(eq(phases.id, id)); + } +} diff --git a/src/db/repositories/drizzle/plan.ts b/src/db/repositories/drizzle/plan.ts new file mode 100644 index 0000000..f116a8c --- /dev/null +++ b/src/db/repositories/drizzle/plan.ts @@ -0,0 +1,82 @@ +/** + * Drizzle Plan Repository Adapter + * + * Implements PlanRepository interface using Drizzle ORM. + */ + +import { eq, asc } from 'drizzle-orm'; +import { nanoid } from 'nanoid'; +import type { DrizzleDatabase } from '../../index.js'; +import { plans, type Plan } from '../../schema.js'; +import type { + PlanRepository, + CreatePlanData, + UpdatePlanData, +} from '../plan-repository.js'; + +/** + * Drizzle adapter for PlanRepository. + * + * Uses dependency injection for database instance, + * enabling isolated test databases. + */ +export class DrizzlePlanRepository implements PlanRepository { + constructor(private db: DrizzleDatabase) {} + + async create(data: CreatePlanData): Promise { + const now = new Date(); + const plan = { + id: nanoid(), + ...data, + createdAt: now, + updatedAt: now, + }; + + await this.db.insert(plans).values(plan); + + return plan as Plan; + } + + async findById(id: string): Promise { + const result = await this.db + .select() + .from(plans) + .where(eq(plans.id, id)) + .limit(1); + + return result[0] ?? null; + } + + async findByPhaseId(phaseId: string): Promise { + return this.db + .select() + .from(plans) + .where(eq(plans.phaseId, phaseId)) + .orderBy(asc(plans.number)); + } + + async update(id: string, data: UpdatePlanData): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Plan not found: ${id}`); + } + + const updated = { + ...data, + updatedAt: new Date(), + }; + + await this.db.update(plans).set(updated).where(eq(plans.id, id)); + + return { ...existing, ...updated } as Plan; + } + + async delete(id: string): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Plan not found: ${id}`); + } + + await this.db.delete(plans).where(eq(plans.id, id)); + } +} diff --git a/src/db/repositories/drizzle/task.ts b/src/db/repositories/drizzle/task.ts new file mode 100644 index 0000000..2d4ad22 --- /dev/null +++ b/src/db/repositories/drizzle/task.ts @@ -0,0 +1,82 @@ +/** + * Drizzle Task Repository Adapter + * + * Implements TaskRepository interface using Drizzle ORM. + */ + +import { eq, asc } from 'drizzle-orm'; +import { nanoid } from 'nanoid'; +import type { DrizzleDatabase } from '../../index.js'; +import { tasks, type Task } from '../../schema.js'; +import type { + TaskRepository, + CreateTaskData, + UpdateTaskData, +} from '../task-repository.js'; + +/** + * Drizzle adapter for TaskRepository. + * + * Uses dependency injection for database instance, + * enabling isolated test databases. + */ +export class DrizzleTaskRepository implements TaskRepository { + constructor(private db: DrizzleDatabase) {} + + async create(data: CreateTaskData): Promise { + const now = new Date(); + const task = { + id: nanoid(), + ...data, + createdAt: now, + updatedAt: now, + }; + + await this.db.insert(tasks).values(task); + + return task as Task; + } + + async findById(id: string): Promise { + const result = await this.db + .select() + .from(tasks) + .where(eq(tasks.id, id)) + .limit(1); + + return result[0] ?? null; + } + + async findByPlanId(planId: string): Promise { + return this.db + .select() + .from(tasks) + .where(eq(tasks.planId, planId)) + .orderBy(asc(tasks.order)); + } + + async update(id: string, data: UpdateTaskData): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Task not found: ${id}`); + } + + const updated = { + ...data, + updatedAt: new Date(), + }; + + await this.db.update(tasks).set(updated).where(eq(tasks.id, id)); + + return { ...existing, ...updated } as Task; + } + + async delete(id: string): Promise { + const existing = await this.findById(id); + if (!existing) { + throw new Error(`Task not found: ${id}`); + } + + await this.db.delete(tasks).where(eq(tasks.id, id)); + } +}