feat: garden care reminder bodies, task sync, upload cleanup
- care reminders now fire with plant-specific body ("Time to water Pothos")
via title/body columns on the reminders table (migration 0016)
- bi-directional task sync: checking off a garden-linked task list item
creates a care log; logging care from garden marks the linked task done
(list_items.metadata stores gardenPlantId + gardenCareType linkage)
- upload error response no longer leaks debug detail; error message
corrected from "5 MB" to "100 MB"
This commit is contained in:
@@ -73,6 +73,26 @@ export function getWidgetMetas(): SerializedWidgetMeta[] {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Item toggle hooks ────────────────────────────────────────────────────────
|
||||
|
||||
type ItemTogglePayload = {
|
||||
itemId: string;
|
||||
metadata: Record<string, unknown>;
|
||||
done: boolean;
|
||||
userId: string;
|
||||
};
|
||||
type ItemToggleHook = (payload: ItemTogglePayload) => Promise<void>;
|
||||
|
||||
const itemToggleHooks = new Map<string, ItemToggleHook>();
|
||||
|
||||
export function registerItemToggleHook(id: string, hook: ItemToggleHook): void {
|
||||
itemToggleHooks.set(id, hook);
|
||||
}
|
||||
|
||||
export async function fireItemToggleHooks(payload: ItemTogglePayload): Promise<void> {
|
||||
await Promise.allSettled([...itemToggleHooks.values()].map((h) => h(payload)));
|
||||
}
|
||||
|
||||
export function getQuickAdds(): SerializedQuickAddItem[] {
|
||||
return [...modules.values()].flatMap((manifest) =>
|
||||
(manifest.quickAdds ?? []).map(({ id, label, icon, url }) => ({
|
||||
|
||||
@@ -13,6 +13,8 @@ export async function scheduleReminder(input: {
|
||||
fireAt: Date;
|
||||
createdBy: string;
|
||||
channel?: string;
|
||||
title?: string;
|
||||
body?: string;
|
||||
}) {
|
||||
await db
|
||||
.insert(reminders)
|
||||
@@ -22,12 +24,20 @@ export async function scheduleReminder(input: {
|
||||
entityId: input.entityId,
|
||||
fireAt: input.fireAt,
|
||||
channel: input.channel ?? "auto",
|
||||
title: input.title ?? null,
|
||||
body: input.body ?? null,
|
||||
createdBy: input.createdBy,
|
||||
firedAt: null,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [reminders.entityType, reminders.entityId],
|
||||
set: { fireAt: input.fireAt, firedAt: null, createdBy: input.createdBy },
|
||||
set: {
|
||||
fireAt: input.fireAt,
|
||||
title: input.title ?? null,
|
||||
body: input.body ?? null,
|
||||
firedAt: null,
|
||||
createdBy: input.createdBy,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,8 +92,8 @@ export async function tickReminders() {
|
||||
if (!reminder.createdBy) return;
|
||||
try {
|
||||
await notify(reminder.createdBy, {
|
||||
title: "Reminder",
|
||||
body: `You have a reminder`,
|
||||
title: reminder.title ?? "Reminder",
|
||||
body: reminder.body ?? "You have a reminder",
|
||||
url: reminder.entityType === "notes.note" ? `/notes/${reminder.entityId}` : "/",
|
||||
channels: ["push", "inapp"],
|
||||
});
|
||||
|
||||
@@ -166,6 +166,8 @@ export const reminders = pgTable(
|
||||
entityId: uuid("entity_id").notNull(),
|
||||
fireAt: timestamp("fire_at", { withTimezone: true }).notNull(),
|
||||
channel: text("channel").notNull().default("auto"),
|
||||
title: text("title"),
|
||||
body: text("body"),
|
||||
firedAt: timestamp("fired_at", { withTimezone: true }),
|
||||
createdBy: uuid("created_by").references(() => users.id, { onDelete: "set null" }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
|
||||
Reference in New Issue
Block a user