feat: backend improvements - reminder tracking, share stock status, planner notifications (#145)

- Separate stock/intake reminder tracking in DB with dedicated columns
- Add shareStockStatus setting to control stock visibility on shared links
- Rewrite planner notification to support both email and Shoutrrr push
- Add push notification footer text for intake and stock reminders
- New DB migrations: stock_reminder_tracking (0006), share_stock_status (0007)
- Update backend i18n with demandCalculator section and critically low text
- Add 514 passing backend tests including new coverage for all changes
This commit is contained in:
Daniel Volz
2026-02-09 19:32:32 +01:00
committed by GitHub
parent 8ff652459d
commit f56f2b7c88
22 changed files with 2815 additions and 286 deletions
+10 -4
View File
@@ -88,10 +88,10 @@ export async function runDrizzleMigrations(
await migrate(database, { migrationsFolder });
return { success: true };
} catch (err: any) {
// If the error is "duplicate column", it means the schema is already up-to-date
// This happens when ALTER migrations in client.ts have already added the columns
// We consider this a success with a warning, not a failure
if (err.message?.includes("duplicate column")) {
// If the error is about existing schema objects, the DB is already up-to-date
// This happens when ALTER migrations in client.ts have already added the columns,
// or when tables were created before drizzle migrations were introduced
if (err.message?.includes("duplicate column") || err.message?.includes("already exists")) {
return { success: true, warning: `Schema already up-to-date: ${err.message}` };
}
return { success: false, error: err.message };
@@ -129,6 +129,12 @@ export async function runAlterMigrations(client: Client): Promise<{ success: boo
`ALTER TABLE medications ADD COLUMN dose_unit text DEFAULT 'mg'`,
// Added for intake-level takenBy: unified intakes structure
`ALTER TABLE medications ADD COLUMN intakes_json text NOT NULL DEFAULT '[]'`,
// Added for separate stock reminder tracking
`ALTER TABLE user_settings ADD COLUMN last_stock_reminder_sent text`,
`ALTER TABLE user_settings ADD COLUMN last_stock_reminder_channel text`,
`ALTER TABLE user_settings ADD COLUMN last_stock_reminder_med_names text`,
// Added for share stock visibility toggle
`ALTER TABLE user_settings ADD COLUMN share_stock_status integer NOT NULL DEFAULT 1`,
];
for (const sql of alterMigrations) {
+7 -1
View File
@@ -86,12 +86,18 @@ export const userSettings = sqliteTable("user_settings", {
language: text("language", { length: 10 }).notNull().default("en"),
// Stock calculation mode: "automatic" (schedule-based) or "manual" (only marked doses)
stockCalculationMode: text("stock_calculation_mode", { length: 20 }).notNull().default("automatic"),
// Last notification tracking
// Whether shared schedule links show stock status (Critical/Low/Normal) to intake users
shareStockStatus: integer("share_stock_status", { mode: "boolean" }).notNull().default(true),
// Last notification tracking (intake reminders)
lastAutoEmailSent: text("last_auto_email_sent"),
lastNotificationType: text("last_notification_type"),
lastNotificationChannel: text("last_notification_channel"),
lastReminderMedName: text("last_reminder_med_name"),
lastReminderTakenBy: text("last_reminder_taken_by"),
// Last stock reminder tracking (separate from intake)
lastStockReminderSent: text("last_stock_reminder_sent"),
lastStockReminderChannel: text("last_stock_reminder_channel"),
lastStockReminderMedNames: text("last_stock_reminder_med_names"),
// Timestamps
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
});