Files
RedditBot/src/database/repositories.ts
T
2026-01-04 19:16:31 +00:00

268 lines
7.6 KiB
TypeScript

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<string, unknown> | 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<string, unknown> | undefined;
if (!row) return undefined;
return this.mapRow(row);
}
getRecent(limit: number = 10): SentMessageRecord[] {
const rows = this.getRecentStmt.all(limit) as Record<string, unknown>[];
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<string, unknown>[];
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<string, unknown>): 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<string, unknown>[];
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;
}
}