268 lines
7.6 KiB
TypeScript
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;
|
|
}
|
|
}
|