import type Database from 'better-sqlite3'; export interface ProcessedPostRecord { id: string; subreddit: string; author: string; title: string; imageUrl: string | null; imageAnalysis: string | null; generatedMessage: string | null; processedAt: number; messageSent: boolean; error: string | null; } export interface SentMessageRecord { id: number; postId: string; recipient: string; subject: string; message: string; sentAt: number; gotResponse: boolean; responseCheckedAt: number | null; } export interface SuccessfulMessageRecord { id: number; sentMessageId: number; originalMessage: string; imageAnalysis: string | null; postTitle: string | null; responseSnippet: string | null; createdAt: number; } export class PostsRepository { private insertStmt: Database.Statement; private existsStmt: Database.Statement; private markSentStmt: Database.Statement; private updateAnalysisStmt: Database.Statement; private setErrorStmt: Database.Statement; private getByIdStmt: Database.Statement; constructor(private db: Database.Database) { this.insertStmt = db.prepare(` INSERT INTO processed_posts (id, subreddit, author, title, image_url, processed_at) VALUES (?, ?, ?, ?, ?, ?) `); this.existsStmt = db.prepare(`SELECT 1 FROM processed_posts WHERE id = ?`); this.markSentStmt = db.prepare(`UPDATE processed_posts SET message_sent = 1 WHERE id = ?`); this.updateAnalysisStmt = db.prepare(` UPDATE processed_posts SET image_analysis = ?, generated_message = ? WHERE id = ? `); this.setErrorStmt = db.prepare(`UPDATE processed_posts SET error = ? WHERE id = ?`); this.getByIdStmt = db.prepare(`SELECT * FROM processed_posts WHERE id = ?`); } isProcessed(postId: string): boolean { return this.existsStmt.get(postId) !== undefined; } insert(post: { id: string; subreddit: string; author: string; title: string; imageUrl: string | null; processedAt: number; }): void { this.insertStmt.run( post.id, post.subreddit, post.author, post.title, post.imageUrl, post.processedAt ); } updateAnalysis(postId: string, analysis: string, message: string): void { this.updateAnalysisStmt.run(analysis, message, postId); } markMessageSent(postId: string): void { this.markSentStmt.run(postId); } setError(postId: string, error: string): void { this.setErrorStmt.run(error, postId); } getById(postId: string): ProcessedPostRecord | undefined { const row = this.getByIdStmt.get(postId) as Record | undefined; if (!row) return undefined; return { id: row.id as string, subreddit: row.subreddit as string, author: row.author as string, title: row.title as string, imageUrl: row.image_url as string | null, imageAnalysis: row.image_analysis as string | null, generatedMessage: row.generated_message as string | null, processedAt: row.processed_at as number, messageSent: Boolean(row.message_sent), error: row.error as string | null, }; } } export class MessagesRepository { private insertStmt: Database.Statement; private getByPostIdStmt: Database.Statement; private getRecentStmt: Database.Statement; private getUncheckedStmt: Database.Statement; private markResponseStmt: Database.Statement; private markCheckedStmt: Database.Statement; constructor(private db: Database.Database) { this.insertStmt = db.prepare(` INSERT INTO sent_messages (post_id, recipient, subject, message, sent_at) VALUES (?, ?, ?, ?, ?) `); this.getByPostIdStmt = db.prepare(`SELECT * FROM sent_messages WHERE post_id = ?`); this.getRecentStmt = db.prepare(` SELECT * FROM sent_messages ORDER BY sent_at DESC LIMIT ? `); this.getUncheckedStmt = db.prepare(` SELECT * FROM sent_messages WHERE got_response = 0 AND (response_checked_at IS NULL OR response_checked_at < ?) AND sent_at < ? ORDER BY sent_at ASC LIMIT ? `); this.markResponseStmt = db.prepare(` UPDATE sent_messages SET got_response = 1, response_checked_at = ? WHERE id = ? `); this.markCheckedStmt = db.prepare(` UPDATE sent_messages SET response_checked_at = ? WHERE id = ? `); } insert(message: { postId: string; recipient: string; subject: string; message: string; sentAt: number; }): number { const result = this.insertStmt.run( message.postId, message.recipient, message.subject, message.message, message.sentAt ); return result.lastInsertRowid as number; } getByPostId(postId: string): SentMessageRecord | undefined { const row = this.getByPostIdStmt.get(postId) as Record | undefined; if (!row) return undefined; return this.mapRow(row); } getRecent(limit: number = 10): SentMessageRecord[] { const rows = this.getRecentStmt.all(limit) as Record[]; return rows.map((row) => this.mapRow(row)); } getUncheckedMessages(minAgeMs: number, recheckAfterMs: number, limit: number = 10): SentMessageRecord[] { const now = Date.now(); const minSentAt = now - minAgeMs; const recheckBefore = now - recheckAfterMs; const rows = this.getUncheckedStmt.all(recheckBefore, minSentAt, limit) as Record[]; return rows.map((row) => this.mapRow(row)); } markGotResponse(messageId: number): void { this.markResponseStmt.run(Date.now(), messageId); } markChecked(messageId: number): void { this.markCheckedStmt.run(Date.now(), messageId); } private mapRow(row: Record): SentMessageRecord { return { id: row.id as number, postId: row.post_id as string, recipient: row.recipient as string, subject: row.subject as string, message: row.message as string, sentAt: row.sent_at as number, gotResponse: Boolean(row.got_response), responseCheckedAt: row.response_checked_at as number | null, }; } } export class SuccessfulMessagesRepository { private insertStmt: Database.Statement; private getRecentStmt: Database.Statement; private countStmt: Database.Statement; constructor(private db: Database.Database) { this.insertStmt = db.prepare(` INSERT INTO successful_messages (sent_message_id, original_message, image_analysis, post_title, response_snippet, created_at) VALUES (?, ?, ?, ?, ?, ?) `); this.getRecentStmt = db.prepare(` SELECT * FROM successful_messages ORDER BY created_at DESC LIMIT ? `); this.countStmt = db.prepare(`SELECT COUNT(*) as count FROM successful_messages`); } insert(data: { sentMessageId: number; originalMessage: string; imageAnalysis: string | null; postTitle: string | null; responseSnippet: string | null; }): void { this.insertStmt.run( data.sentMessageId, data.originalMessage, data.imageAnalysis, data.postTitle, data.responseSnippet, Date.now() ); } getRecent(limit: number = 5): SuccessfulMessageRecord[] { const rows = this.getRecentStmt.all(limit) as Record[]; return rows.map((row) => ({ id: row.id as number, sentMessageId: row.sent_message_id as number, originalMessage: row.original_message as string, imageAnalysis: row.image_analysis as string | null, postTitle: row.post_title as string | null, responseSnippet: row.response_snippet as string | null, createdAt: row.created_at as number, })); } count(): number { const result = this.countStmt.get() as { count: number }; return result.count; } }