Implement activity log (task 22)
- activity_log table in _core/schema.ts with household+created_at index; migration 0008_activity_log.sql applied - logActivity() in _core/activity.ts reads current session and inserts a row - ActivityLogEntry type + renderActivity?(entry): string added to EntityTypeRegistration so modules declare human-readable labels without any if/else branches in the widget - core.activity widget replaced with a real async server component that queries the last 20 rows and renders via the registry - logActivity wired into every create/update/delete in calendar, lists, and notes server actions; getAuthorizedItem also returns text so toggle/delete can include item text in the payload - pnpm typecheck, lint, build, and all 4 E2E specs pass Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1133fa8aac
commit
28475e483d
@@ -45,6 +45,14 @@ const manifest: ModuleManifest = {
|
||||
reminder: { canRemind: true },
|
||||
search: { search: searchNotes },
|
||||
resolveUrl: (id) => `/notes/${id}`,
|
||||
renderActivity: (entry) => {
|
||||
const title = entry.payload?.title as string | undefined;
|
||||
if (entry.action === "create") return `Created note${title ? ` "${title}"` : ""}`;
|
||||
if (entry.action === "delete") return `Deleted note${title ? ` "${title}"` : ""}`;
|
||||
if (entry.action === "pin") return `Pinned note${title ? ` "${title}"` : ""}`;
|
||||
if (entry.action === "unpin") return `Unpinned note${title ? ` "${title}"` : ""}`;
|
||||
return `Updated note${title ? ` "${title}"` : ""}`;
|
||||
},
|
||||
},
|
||||
],
|
||||
dashboardWidgets: [
|
||||
|
||||
@@ -5,6 +5,7 @@ import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { getCurrentSession } from "@/lib/session";
|
||||
import { logActivity } from "@/modules/_core/activity";
|
||||
import { reminders } from "@/modules/_core/schema";
|
||||
import { notes } from "../schema";
|
||||
import { canAccessNote, getNote } from "./queries";
|
||||
@@ -50,6 +51,7 @@ export async function createNote(input: z.input<typeof noteInput>) {
|
||||
return [created];
|
||||
});
|
||||
|
||||
if (note) await logActivity({ entityType: "notes.note", entityId: note.id, action: "create", payload: { title: note.title } });
|
||||
revalidatePath("/notes");
|
||||
return note;
|
||||
}
|
||||
@@ -83,6 +85,7 @@ export async function updateNote(input: z.input<typeof updateNoteInput>) {
|
||||
return [updated];
|
||||
});
|
||||
|
||||
if (note) await logActivity({ entityType: "notes.note", entityId: note.id, action: "update", payload: { title: note.title } });
|
||||
revalidatePath("/notes");
|
||||
revalidatePath(`/notes/${parsed.id}`);
|
||||
return note;
|
||||
@@ -98,9 +101,11 @@ export async function setNotePinned(input: { id: string; pinned: boolean }) {
|
||||
.set({ pinned: parsed.pinned, updatedAt: new Date() })
|
||||
.where(eq(notes.id, parsed.id));
|
||||
|
||||
const note = await getNote(parsed.id);
|
||||
await logActivity({ entityType: "notes.note", entityId: parsed.id, action: parsed.pinned ? "pin" : "unpin", payload: note ? { title: note.title } : undefined });
|
||||
revalidatePath("/notes");
|
||||
revalidatePath(`/notes/${parsed.id}`);
|
||||
return getNote(parsed.id);
|
||||
return note;
|
||||
}
|
||||
|
||||
export async function deleteNote(input: { id: string }) {
|
||||
@@ -108,6 +113,9 @@ export async function deleteNote(input: { id: string }) {
|
||||
const { household } = await getCurrentSession();
|
||||
await assertCanAccessNote(parsed.id, household.id);
|
||||
|
||||
const note = await getNote(parsed.id);
|
||||
await logActivity({ entityType: "notes.note", entityId: parsed.id, action: "delete", payload: note ? { title: note.title } : undefined });
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(reminders)
|
||||
|
||||
Reference in New Issue
Block a user