From f9c20b2a17abfd96ee7d85e458700564f5def4ba Mon Sep 17 00:00:00 2001 From: Sjoerd de Vries Date: Wed, 20 May 2026 19:07:38 +0200 Subject: [PATCH] fix: reschedule upsert, settings restart warning, unified streak util Co-Authored-By: Claude Sonnet 4.6 --- src/ai/context.ts | 33 ++------------------------------ src/bot/commands/settings-cmd.ts | 5 ++++- src/bot/commands/streak.ts | 26 +------------------------ src/db/schedule.ts | 2 +- src/utils/streak.ts | 25 ++++++++++++++++++++++++ 5 files changed, 33 insertions(+), 58 deletions(-) create mode 100644 src/utils/streak.ts diff --git a/src/ai/context.ts b/src/ai/context.ts index a5c76cb..e1fdc17 100644 --- a/src/ai/context.ts +++ b/src/ai/context.ts @@ -2,6 +2,7 @@ import type { Database } from 'better-sqlite3'; import { getRecentSessions, type Session } from '../db/sessions'; import { getUpcomingSchedule, type ScheduleEntry } from '../db/schedule'; import { getSetting } from '../db/settings'; +import { getStreaks } from '../utils/streak'; export interface AIContext { today: string; @@ -12,41 +13,11 @@ export interface AIContext { recentSkips: Array<{ date: string; reason: string | null }>; } -function calculateStreak(db: Database): number { - const rows = db.prepare( - "SELECT date FROM schedule WHERE status = 'completed' ORDER BY date DESC" - ).all() as Array<{ date: string }>; - - if (rows.length === 0) return 0; - - let streak = 0; - let prev: string | null = null; - - for (const { date } of rows) { - if (prev === null) { - streak = 1; - prev = date; - continue; - } - const prevDate = new Date(prev + 'T12:00:00'); - const currDate = new Date(date + 'T12:00:00'); - const diffDays = Math.round((prevDate.getTime() - currDate.getTime()) / 86400000); - if (diffDays <= 2) { - streak++; - prev = date; - } else { - break; - } - } - - return streak; -} - export function buildContext(db: Database, today: string): AIContext { const recentSessions = getRecentSessions(db, 30); const upcomingSchedule = getUpcomingSchedule(db, today); const goal = getSetting(db, 'goal') ?? 'Not set'; - const currentStreak = calculateStreak(db); + const currentStreak = getStreaks(db).current; const recentSkips = (db.prepare( "SELECT date, skip_reason as reason FROM schedule WHERE status = 'skipped' ORDER BY date DESC LIMIT 10" diff --git a/src/bot/commands/settings-cmd.ts b/src/bot/commands/settings-cmd.ts index 59bfec4..297f43b 100644 --- a/src/bot/commands/settings-cmd.ts +++ b/src/bot/commands/settings-cmd.ts @@ -30,6 +30,9 @@ export function handleSettings(db: Database) { } setSetting(db, key, value); - await ctx.reply(`Updated ${key} = ${value}`); + const restartNote = (key === 'morning_reminder' || key === 'evening_reminder') + ? '\n\n⚠️ Restart the bot for the new reminder time to take effect.' + : ''; + await ctx.reply(`Updated ${key} = ${value}${restartNote}`); }; } diff --git a/src/bot/commands/streak.ts b/src/bot/commands/streak.ts index 5d4a2b4..731468b 100644 --- a/src/bot/commands/streak.ts +++ b/src/bot/commands/streak.ts @@ -1,30 +1,6 @@ import type { Context } from 'telegraf'; import type { Database } from 'better-sqlite3'; - -function getStreaks(db: Database): { current: number; longest: number } { - const rows = db.prepare( - "SELECT date FROM schedule WHERE status = 'completed' ORDER BY date ASC" - ).all() as Array<{ date: string }>; - - if (rows.length === 0) return { current: 0, longest: 0 }; - - let current = 1, longest = 1, streak = 1; - - for (let i = 1; i < rows.length; i++) { - const prev = new Date(rows[i - 1]!.date + 'T12:00:00'); - const curr = new Date(rows[i]!.date + 'T12:00:00'); - const diff = Math.round((curr.getTime() - prev.getTime()) / 86400000); - if (diff <= 2) { - streak++; - if (streak > longest) longest = streak; - } else { - streak = 1; - } - } - - current = streak; - return { current, longest }; -} +import { getStreaks } from '../../utils/streak'; export function handleStreak(db: Database) { return async (ctx: Context) => { diff --git a/src/db/schedule.ts b/src/db/schedule.ts index 503bcec..0b6b547 100644 --- a/src/db/schedule.ts +++ b/src/db/schedule.ts @@ -16,7 +16,7 @@ export function insertScheduleEntry( db: Database, date: string, planned_min: number, ai_rationale: string | null ): ScheduleEntry { db.prepare( - 'INSERT OR IGNORE INTO schedule (date, planned_min, ai_rationale) VALUES (?, ?, ?)' + 'INSERT OR REPLACE INTO schedule (date, planned_min, ai_rationale, status) VALUES (?, ?, ?, \'pending\')' ).run(date, planned_min, ai_rationale); return getScheduleByDate(db, date)!; } diff --git a/src/utils/streak.ts b/src/utils/streak.ts new file mode 100644 index 0000000..5e97792 --- /dev/null +++ b/src/utils/streak.ts @@ -0,0 +1,25 @@ +import type { Database } from 'better-sqlite3'; + +export function getStreaks(db: Database): { current: number; longest: number } { + const rows = db.prepare( + "SELECT date FROM schedule WHERE status = 'completed' ORDER BY date ASC" + ).all() as Array<{ date: string }>; + + if (rows.length === 0) return { current: 0, longest: 0 }; + + let longest = 1, streak = 1; + + for (let i = 1; i < rows.length; i++) { + const prev = new Date(rows[i - 1]!.date + 'T12:00:00'); + const curr = new Date(rows[i]!.date + 'T12:00:00'); + const diff = Math.round((curr.getTime() - prev.getTime()) / 86400000); + if (diff <= 2) { + streak++; + if (streak > longest) longest = streak; + } else { + streak = 1; + } + } + + return { current: streak, longest }; +}