/** * Drizzle Message Repository Adapter * * Implements MessageRepository interface using Drizzle ORM. */ import { eq, and, desc } from 'drizzle-orm'; import { nanoid } from 'nanoid'; import type { DrizzleDatabase } from '../../index.js'; import { messages, type Message } from '../../schema.js'; import type { MessageRepository, CreateMessageData, UpdateMessageData, MessageParticipantType, } from '../message-repository.js'; /** * Drizzle adapter for MessageRepository. * * Uses dependency injection for database instance, * enabling isolated test databases. */ export class DrizzleMessageRepository implements MessageRepository { constructor(private db: DrizzleDatabase) {} async create(data: CreateMessageData): Promise { const id = nanoid(); const now = new Date(); const [created] = await this.db.insert(messages).values({ id, senderType: data.senderType, senderId: data.senderId ?? null, recipientType: data.recipientType, recipientId: data.recipientId ?? null, type: data.type ?? 'info', content: data.content, requiresResponse: data.requiresResponse ?? false, status: data.status ?? 'pending', parentMessageId: data.parentMessageId ?? null, createdAt: now, updatedAt: now, }).returning(); return created; } async findById(id: string): Promise { const result = await this.db .select() .from(messages) .where(eq(messages.id, id)) .limit(1); return result[0] ?? null; } async findBySender(type: MessageParticipantType, id?: string): Promise { if (id) { return this.db .select() .from(messages) .where(and(eq(messages.senderType, type), eq(messages.senderId, id))) .orderBy(desc(messages.createdAt)); } // For user sender (no id), find where senderType='user' and senderId is null return this.db .select() .from(messages) .where(eq(messages.senderType, type)) .orderBy(desc(messages.createdAt)); } async findByRecipient(type: MessageParticipantType, id?: string): Promise { if (id) { return this.db .select() .from(messages) .where(and(eq(messages.recipientType, type), eq(messages.recipientId, id))) .orderBy(desc(messages.createdAt)); } // For user recipient (no id), find where recipientType='user' and recipientId is null return this.db .select() .from(messages) .where(eq(messages.recipientType, type)) .orderBy(desc(messages.createdAt)); } async findPendingForUser(): Promise { return this.db .select() .from(messages) .where(and(eq(messages.recipientType, 'user'), eq(messages.status, 'pending'))) .orderBy(desc(messages.createdAt)); } async findRequiringResponse(): Promise { return this.db .select() .from(messages) .where(and(eq(messages.requiresResponse, true), eq(messages.status, 'pending'))) .orderBy(desc(messages.createdAt)); } async findReplies(parentMessageId: string): Promise { return this.db .select() .from(messages) .where(eq(messages.parentMessageId, parentMessageId)) .orderBy(desc(messages.createdAt)); } async update(id: string, data: UpdateMessageData): Promise { const [updated] = await this.db .update(messages) .set({ ...data, updatedAt: new Date() }) .where(eq(messages.id, id)) .returning(); if (!updated) { throw new Error(`Message not found: ${id}`); } return updated; } async delete(id: string): Promise { const [deleted] = await this.db.delete(messages).where(eq(messages.id, id)).returning(); if (!deleted) { throw new Error(`Message not found: ${id}`); } } }