Update source code and remove runtime data from tracking
Add gitignore rules for browser session data, SQLite databases, media downloads, and build artifacts. Remove previously tracked runtime files from the repository. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e90213d1ff
commit
dfa0580859
23 files changed
+5324
-347
No files matched your search
+157
-15
@@ -22,7 +22,10 @@ export interface SentMessageRecord {
|
||||
sentAt: number;
|
||||
gotResponse: boolean;
|
||||
responseCheckedAt: number | null;
|
||||
strategyId: string | null;
|
||||
subreddit?: string;
|
||||
postTitle?: string;
|
||||
postImageUrl?: string;
|
||||
}
|
||||
|
||||
export interface SuccessfulMessageRecord {
|
||||
@@ -32,9 +35,31 @@ export interface SuccessfulMessageRecord {
|
||||
imageAnalysis: string | null;
|
||||
postTitle: string | null;
|
||||
responseSnippet: string | null;
|
||||
subreddit: string | null;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export interface StrategyStats {
|
||||
strategyId: string;
|
||||
totalSent: number;
|
||||
totalResponses: number;
|
||||
responseRate: number;
|
||||
}
|
||||
|
||||
export interface SubredditStats {
|
||||
subreddit: string;
|
||||
totalSent: number;
|
||||
totalResponses: number;
|
||||
responseRate: number;
|
||||
}
|
||||
|
||||
export interface DailyTrend {
|
||||
date: string;
|
||||
totalSent: number;
|
||||
totalResponses: number;
|
||||
responseRate: number;
|
||||
}
|
||||
|
||||
export class PostsRepository {
|
||||
private insertStmt: Database.Statement;
|
||||
private existsStmt: Database.Statement;
|
||||
@@ -42,6 +67,7 @@ export class PostsRepository {
|
||||
private updateAnalysisStmt: Database.Statement;
|
||||
private setErrorStmt: Database.Statement;
|
||||
private getByIdStmt: Database.Statement;
|
||||
private getByAuthorStmt: Database.Statement;
|
||||
|
||||
constructor(private db: Database.Database) {
|
||||
this.insertStmt = db.prepare(`
|
||||
@@ -62,6 +88,10 @@ export class PostsRepository {
|
||||
this.setErrorStmt = db.prepare(`UPDATE processed_posts SET error = ? WHERE id = ?`);
|
||||
|
||||
this.getByIdStmt = db.prepare(`SELECT * FROM processed_posts WHERE id = ?`);
|
||||
|
||||
this.getByAuthorStmt = db.prepare(`
|
||||
SELECT * FROM processed_posts WHERE author = ? COLLATE NOCASE ORDER BY processed_at DESC LIMIT 10
|
||||
`);
|
||||
}
|
||||
|
||||
isProcessed(postId: string): boolean {
|
||||
@@ -115,6 +145,22 @@ export class PostsRepository {
|
||||
error: row.error as string | null,
|
||||
};
|
||||
}
|
||||
|
||||
getByAuthor(author: string): ProcessedPostRecord[] {
|
||||
const rows = this.getByAuthorStmt.all(author) as Record<string, unknown>[];
|
||||
return rows.map((row) => ({
|
||||
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 {
|
||||
@@ -128,14 +174,14 @@ export class MessagesRepository {
|
||||
|
||||
constructor(private db: Database.Database) {
|
||||
this.insertStmt = db.prepare(`
|
||||
INSERT INTO sent_messages (post_id, recipient, subject, message, sent_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT INTO sent_messages (post_id, recipient, subject, message, sent_at, strategy_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
this.getByPostIdStmt = db.prepare(`SELECT * FROM sent_messages WHERE post_id = ?`);
|
||||
|
||||
this.getRecentStmt = db.prepare(`
|
||||
SELECT sm.*, pp.subreddit
|
||||
SELECT sm.*, pp.subreddit, pp.title AS post_title, pp.image_url AS post_image_url
|
||||
FROM sent_messages sm
|
||||
LEFT JOIN processed_posts pp ON sm.post_id = pp.id
|
||||
ORDER BY sm.sent_at DESC
|
||||
@@ -170,13 +216,15 @@ export class MessagesRepository {
|
||||
subject: string;
|
||||
message: string;
|
||||
sentAt: number;
|
||||
strategyId?: string | null;
|
||||
}): number {
|
||||
const result = this.insertStmt.run(
|
||||
message.postId,
|
||||
message.recipient,
|
||||
message.subject,
|
||||
message.message,
|
||||
message.sentAt
|
||||
message.sentAt,
|
||||
message.strategyId || null
|
||||
);
|
||||
return result.lastInsertRowid as number;
|
||||
}
|
||||
@@ -212,6 +260,72 @@ export class MessagesRepository {
|
||||
return this.hasSentToStmt.get(recipient) !== undefined;
|
||||
}
|
||||
|
||||
getStrategyStats(): StrategyStats[] {
|
||||
const rows = this.db.prepare(`
|
||||
SELECT
|
||||
strategy_id,
|
||||
COUNT(*) as total_sent,
|
||||
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses
|
||||
FROM sent_messages
|
||||
WHERE strategy_id IS NOT NULL
|
||||
GROUP BY strategy_id
|
||||
`).all() as Record<string, unknown>[];
|
||||
|
||||
return rows.map(row => ({
|
||||
strategyId: row.strategy_id as string,
|
||||
totalSent: row.total_sent as number,
|
||||
totalResponses: row.total_responses as number,
|
||||
responseRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
getSubredditStats(): SubredditStats[] {
|
||||
const rows = this.db.prepare(`
|
||||
SELECT
|
||||
pp.subreddit,
|
||||
COUNT(*) as total_sent,
|
||||
SUM(CASE WHEN sm.got_response = 1 THEN 1 ELSE 0 END) as total_responses
|
||||
FROM sent_messages sm
|
||||
JOIN processed_posts pp ON sm.post_id = pp.id
|
||||
GROUP BY pp.subreddit
|
||||
ORDER BY total_sent DESC
|
||||
`).all() as Record<string, unknown>[];
|
||||
|
||||
return rows.map(row => ({
|
||||
subreddit: row.subreddit as string,
|
||||
totalSent: row.total_sent as number,
|
||||
totalResponses: row.total_responses as number,
|
||||
responseRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
getResponseRateTrend(days: number = 30): DailyTrend[] {
|
||||
const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
|
||||
const rows = this.db.prepare(`
|
||||
SELECT
|
||||
date(sent_at / 1000, 'unixepoch') as day,
|
||||
COUNT(*) as total_sent,
|
||||
SUM(CASE WHEN got_response = 1 THEN 1 ELSE 0 END) as total_responses
|
||||
FROM sent_messages
|
||||
WHERE sent_at > ?
|
||||
GROUP BY day
|
||||
ORDER BY day ASC
|
||||
`).all(cutoff) as Record<string, unknown>[];
|
||||
|
||||
return rows.map(row => ({
|
||||
date: row.day as string,
|
||||
totalSent: row.total_sent as number,
|
||||
totalResponses: row.total_responses as number,
|
||||
responseRate: (row.total_sent as number) > 0
|
||||
? Math.round(((row.total_responses as number) / (row.total_sent as number)) * 100)
|
||||
: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
private mapRow(row: Record<string, unknown>): SentMessageRecord {
|
||||
return {
|
||||
id: row.id as number,
|
||||
@@ -222,7 +336,10 @@ export class MessagesRepository {
|
||||
sentAt: row.sent_at as number,
|
||||
gotResponse: Boolean(row.got_response),
|
||||
responseCheckedAt: row.response_checked_at as number | null,
|
||||
strategyId: (row.strategy_id as string) || null,
|
||||
subreddit: (row.subreddit as string) || undefined,
|
||||
postTitle: (row.post_title as string) || undefined,
|
||||
postImageUrl: (row.post_image_url as string) || undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -230,19 +347,29 @@ export class MessagesRepository {
|
||||
export class SuccessfulMessagesRepository {
|
||||
private insertStmt: Database.Statement;
|
||||
private getRecentStmt: Database.Statement;
|
||||
private getBySubredditStmt: Database.Statement;
|
||||
private countStmt: Database.Statement;
|
||||
private existsByMessageStmt: 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 (?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO successful_messages (sent_message_id, original_message, image_analysis, post_title, response_snippet, subreddit, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
this.getRecentStmt = db.prepare(`
|
||||
SELECT * FROM successful_messages ORDER BY created_at DESC LIMIT ?
|
||||
`);
|
||||
|
||||
// Get examples prioritizing same subreddit, then falling back to others
|
||||
this.getBySubredditStmt = db.prepare(`
|
||||
SELECT *,
|
||||
CASE WHEN subreddit = ? THEN 1 ELSE 0 END as subreddit_match
|
||||
FROM successful_messages
|
||||
ORDER BY subreddit_match DESC, created_at DESC
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
this.countStmt = db.prepare(`SELECT COUNT(*) as count FROM successful_messages`);
|
||||
|
||||
this.existsByMessageStmt = db.prepare(`
|
||||
@@ -256,6 +383,7 @@ export class SuccessfulMessagesRepository {
|
||||
imageAnalysis: string | null;
|
||||
postTitle: string | null;
|
||||
responseSnippet: string | null;
|
||||
subreddit: string | null;
|
||||
}): void {
|
||||
this.insertStmt.run(
|
||||
data.sentMessageId,
|
||||
@@ -263,6 +391,7 @@ export class SuccessfulMessagesRepository {
|
||||
data.imageAnalysis,
|
||||
data.postTitle,
|
||||
data.responseSnippet,
|
||||
data.subreddit,
|
||||
Date.now()
|
||||
);
|
||||
}
|
||||
@@ -273,19 +402,32 @@ export class SuccessfulMessagesRepository {
|
||||
|
||||
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,
|
||||
}));
|
||||
return rows.map((row) => this.mapRow(row));
|
||||
}
|
||||
|
||||
getWeightedExamples(subreddit: string | null, limit: number = 3): SuccessfulMessageRecord[] {
|
||||
if (subreddit) {
|
||||
const rows = this.getBySubredditStmt.all(subreddit, limit) as Record<string, unknown>[];
|
||||
return rows.map((row) => this.mapRow(row));
|
||||
}
|
||||
return this.getRecent(limit);
|
||||
}
|
||||
|
||||
count(): number {
|
||||
const result = this.countStmt.get() as { count: number };
|
||||
return result.count;
|
||||
}
|
||||
|
||||
private mapRow(row: Record<string, unknown>): SuccessfulMessageRecord {
|
||||
return {
|
||||
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,
|
||||
subreddit: row.subreddit as string | null,
|
||||
createdAt: row.created_at as number,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user