Skip to content

Commit 44dd746

Browse files
authored
Merge pull request #86 from techulus/feat/roadmap-audit-logs
Add audit logs for roadmaps
2 parents 5453404 + 78e4910 commit 44dd746

File tree

10 files changed

+215
-10
lines changed

10 files changed

+215
-10
lines changed

apps/web/components/post/post.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { notifyError } from "../core/toast.component";
2323
import ConfirmDeleteDialog from "../dialogs/confirm-delete-dialog.component";
2424
import PostOptions from "./post-options";
2525
import { PostStatusIcon } from "./post-status";
26+
import { createAuditLog } from "../../utils/auditLog";
2627

2728
const ReactionsCounter = ({ aggregate }: { aggregate: IReactions }) => {
2829
return (
@@ -171,7 +172,7 @@ export function Post({
171172
try {
172173
await supabase.from("posts").delete().eq("id", post.id);
173174

174-
await supabase.from("page_audit_logs").insert({
175+
await createAuditLog(supabase, {
175176
page_id: page.id,
176177
actor_id: user.id,
177178
action: "Deleted Post",

apps/web/components/roadmap/RoadmapBoard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default function RoadmapBoard({
3636
const dragDropHandlers = useRoadmapDragDrop({
3737
itemsByColumn,
3838
setBoardItems,
39+
board,
3940
});
4041

4142
const itemHandlers = useRoadmapItems({

apps/web/components/roadmap/hooks/useRoadmapDragDrop.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import { Dispatch, SetStateAction, useState } from "react";
22
import { useUserData } from "../../../utils/useUser";
3+
import { createAuditLog } from "../../../utils/auditLog";
34
import {
45
DragOverPosition,
56
ItemsByColumn,
67
RoadmapItemWithRelations,
78
} from "../types";
9+
import { IRoadmapBoard } from "@changes-page/supabase/types/page";
810

911
export function useRoadmapDragDrop({
1012
itemsByColumn,
1113
setBoardItems,
14+
board,
1215
}: {
1316
itemsByColumn: ItemsByColumn;
1417
setBoardItems: Dispatch<SetStateAction<RoadmapItemWithRelations[]>>;
18+
board: IRoadmapBoard;
1519
}) {
16-
const { supabase } = useUserData();
20+
const { supabase, user } = useUserData();
1721
const [draggedItem, setDraggedItem] =
1822
useState<RoadmapItemWithRelations | null>(null);
1923
const [dragOverColumn, setDragOverColumn] = useState<string | null>(null);
@@ -90,6 +94,26 @@ export function useRoadmapDragDrop({
9094
currentDragOverPosition
9195
);
9296
}
97+
98+
// Create audit log for item move
99+
if (user && draggedItem) {
100+
const action = sourceColumnId === targetColumnId ? "Reordered" : "Moved";
101+
const description = sourceColumnId === targetColumnId
102+
? `${action} item within column`
103+
: `${action} item from column to column`;
104+
105+
await createAuditLog(supabase, {
106+
page_id: board.page_id,
107+
actor_id: user.id,
108+
action: `Updated Roadmap Item: ${draggedItem.title}`,
109+
changes: {
110+
action: description,
111+
from_column: sourceColumnId,
112+
to_column: targetColumnId,
113+
item: draggedItem
114+
},
115+
});
116+
}
93117
} catch (error) {
94118
console.error("Error moving item:", error);
95119
alert("Failed to move item");

apps/web/components/roadmap/hooks/useRoadmapItems.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
IRoadmapCategory,
44
} from "@changes-page/supabase/types/page";
55
import { useState } from "react";
6+
import { createAuditLog } from "../../../utils/auditLog";
67
import { useUserData } from "../../../utils/useUser";
78
import {
89
FormErrors,
@@ -20,7 +21,7 @@ export function useRoadmapItems({
2021
categories: IRoadmapCategory[];
2122
itemsByColumn: ItemsByColumn;
2223
}) {
23-
const { supabase } = useUserData();
24+
const { supabase, user } = useUserData();
2425
const [showItemModal, setShowItemModal] = useState(false);
2526
const [selectedColumnId, setSelectedColumnId] = useState<string | null>(null);
2627
const [editingItem, setEditingItem] =
@@ -66,6 +67,10 @@ export function useRoadmapItems({
6667
if (!confirm("Are you sure you want to delete this item?")) return;
6768

6869
try {
70+
const itemToDelete = Object.values(itemsByColumn)
71+
.flat()
72+
.find((it) => it.id === itemId);
73+
6974
const { error } = await supabase
7075
.from("roadmap_items")
7176
.delete()
@@ -74,6 +79,15 @@ export function useRoadmapItems({
7479
if (error) throw error;
7580

7681
setBoardItems((prev) => prev.filter((item) => item.id !== itemId));
82+
83+
if (itemToDelete && user) {
84+
await createAuditLog(supabase, {
85+
page_id: board.page_id,
86+
actor_id: user.id,
87+
action: `Deleted Roadmap Item: ${itemToDelete.title}`,
88+
changes: { item_id: itemToDelete.id, item_title: itemToDelete.title },
89+
});
90+
}
7791
} catch (error) {
7892
console.error("Error deleting item:", error);
7993
alert("Failed to delete item");
@@ -127,6 +141,19 @@ export function useRoadmapItems({
127141
setBoardItems((prev) =>
128142
prev.map((item) => (item.id === editingItem.id ? data : item))
129143
);
144+
145+
// Create audit log for update
146+
if (user) {
147+
await createAuditLog(supabase, {
148+
page_id: board.page_id,
149+
actor_id: user.id,
150+
action: `Updated Roadmap Item: ${data.title}`,
151+
changes: {
152+
old: editingItem,
153+
new: data,
154+
},
155+
});
156+
}
130157
} else {
131158
if (!selectedColumnId) return;
132159

@@ -162,6 +189,16 @@ export function useRoadmapItems({
162189
if (error) throw error;
163190

164191
setBoardItems((prev) => [...prev, data]);
192+
193+
// Create audit log for creation
194+
if (user) {
195+
await createAuditLog(supabase, {
196+
page_id: board.page_id,
197+
actor_id: user.id,
198+
action: `Created Roadmap Item: ${data.title}`,
199+
changes: { item: data },
200+
});
201+
}
165202
}
166203

167204
setShowItemModal(false);

apps/web/pages/api/posts/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NewPostSchema } from "../../../data/schema";
33
import { apiRateLimiter } from "../../../utils/rate-limit";
44
import { createPost } from "../../../utils/useDatabase";
55
import { withAuth } from "../../../utils/withAuth";
6+
import { createAuditLog } from "../../../utils/auditLog";
67

78
const createNewPost = withAuth(async (req, res, { user, supabase }) => {
89
if (req.method === "POST") {
@@ -73,7 +74,7 @@ const createNewPost = withAuth(async (req, res, { user, supabase }) => {
7374

7475
const post = await createPost(postPayload);
7576

76-
await supabase.from("page_audit_logs").insert({
77+
await createAuditLog(supabase, {
7778
page_id,
7879
actor_id: user.id,
7980
action: `Created Post: ${post.title}`,

apps/web/pages/pages/[page_id]/[post_id].tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { NewPostSchema } from "../../../data/schema";
1414
import { withSupabase } from "../../../utils/supabase/withSupabase";
1515
import { createOrRetrievePageSettings } from "../../../utils/useDatabase";
1616
import { useUserData } from "../../../utils/useUser";
17+
import { createAuditLog } from "../../../utils/auditLog";
1718

1819
export const getServerSideProps = withSupabase(async (ctx, { supabase }) => {
1920
const { page_id, post_id } = ctx.params;
@@ -67,7 +68,7 @@ export default function EditPost({
6768

6869
await supabase.from("posts").update(newPost).match({ id: post_id });
6970

70-
await supabase.from("page_audit_logs").insert({
71+
await createAuditLog(supabase, {
7172
page_id: String(page_id),
7273
actor_id: user.id,
7374
action: `Updated Post: ${newPost.title}`,

apps/web/pages/pages/[page_id]/roadmap/[board_id]/settings.tsx

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { withSupabase } from "../../../../../utils/supabase/withSupabase";
1616
import { createOrRetrievePageSettings } from "../../../../../utils/useDatabase";
1717
import { getPage } from "../../../../../utils/useSSR";
1818
import { useUserData } from "../../../../../utils/useUser";
19+
import { createAuditLog } from "../../../../../utils/auditLog";
1920

2021
export const getServerSideProps = withSupabase(async (ctx, { supabase }) => {
2122
const { page_id } = ctx.params;
@@ -235,6 +236,27 @@ export default function BoardSettings({
235236
throw error;
236237
}
237238

239+
// Create audit log for board update
240+
await createAuditLog(supabase, {
241+
page_id: page_id,
242+
actor_id: user.id,
243+
action: `Updated Roadmap Board: ${boardForm.title}`,
244+
changes: {
245+
old: {
246+
title: board.title,
247+
description: board.description,
248+
slug: board.slug,
249+
is_public: board.is_public
250+
},
251+
new: {
252+
title: boardForm.title,
253+
description: boardForm.description,
254+
slug: boardForm.slug,
255+
is_public: boardForm.is_public
256+
}
257+
},
258+
});
259+
238260
// Refresh the page to show updated settings
239261
window.location.reload();
240262
} catch (error) {
@@ -266,6 +288,14 @@ export default function BoardSettings({
266288
setBoardCategories([...boardCategories, data]);
267289
setNewCategory("");
268290
setNewCategoryColor("blue");
291+
292+
// Create audit log for category creation
293+
await createAuditLog(supabase, {
294+
page_id: page_id,
295+
actor_id: user.id,
296+
action: `Created Roadmap Category: ${data.name}`,
297+
changes: { category: data },
298+
});
269299
} catch (error) {
270300
console.error("Error adding category:", error);
271301
alert("Failed to add category");
@@ -286,20 +316,38 @@ export default function BoardSettings({
286316

287317
if (error) throw error;
288318

319+
const oldCategory = boardCategories.find(cat => cat.id === categoryId);
320+
const newCategoryData = {
321+
name: categoryToEdit.trim(),
322+
color: categoryColorToEdit,
323+
};
324+
289325
setBoardCategories((prev) =>
290326
prev.map((cat) =>
291327
cat.id === categoryId
292328
? {
293329
...cat,
294-
name: categoryToEdit.trim(),
295-
color: categoryColorToEdit,
330+
...newCategoryData,
296331
}
297332
: cat
298333
)
299334
);
300335
setEditingCategory(null);
301336
setCategoryToEdit("");
302337
setCategoryColorToEdit("blue");
338+
339+
// Create audit log for category update
340+
if (oldCategory) {
341+
await createAuditLog(supabase, {
342+
page_id: page_id,
343+
actor_id: user.id,
344+
action: `Updated Roadmap Category: ${newCategoryData.name}`,
345+
changes: {
346+
old: oldCategory,
347+
new: { ...oldCategory, ...newCategoryData }
348+
},
349+
});
350+
}
303351
} catch (error) {
304352
console.error("Error updating category:", error);
305353
alert("Failed to update category");
@@ -332,7 +380,18 @@ export default function BoardSettings({
332380

333381
if (error) throw error;
334382

383+
const deletedCategory = boardCategories.find(cat => cat.id === categoryId);
335384
setBoardCategories((prev) => prev.filter((cat) => cat.id !== categoryId));
385+
386+
// Create audit log for category deletion
387+
if (deletedCategory) {
388+
await createAuditLog(supabase, {
389+
page_id: page_id,
390+
actor_id: user.id,
391+
action: `Deleted Roadmap Category: ${deletedCategory.name}`,
392+
changes: { category: deletedCategory },
393+
});
394+
}
336395
} catch (error) {
337396
console.error("Error deleting category:", error);
338397
alert("Failed to delete category");
@@ -362,6 +421,14 @@ export default function BoardSettings({
362421

363422
setBoardColumns([...boardColumns, data]);
364423
setNewColumn("");
424+
425+
// Create audit log for column creation
426+
await createAuditLog(supabase, {
427+
page_id: page_id,
428+
actor_id: user.id,
429+
action: `Created Roadmap Column: ${data.name}`,
430+
changes: { column: data },
431+
});
365432
} catch (error) {
366433
console.error("Error adding column:", error);
367434
alert("Failed to add column");
@@ -379,13 +446,29 @@ export default function BoardSettings({
379446

380447
if (error) throw error;
381448

449+
const oldColumn = boardColumns.find(col => col.id === columnId);
450+
const newColumnName = columnToEdit.trim();
451+
382452
setBoardColumns((prev) =>
383453
prev.map((col) =>
384-
col.id === columnId ? { ...col, name: columnToEdit.trim() } : col
454+
col.id === columnId ? { ...col, name: newColumnName } : col
385455
)
386456
);
387457
setEditingColumn(null);
388458
setColumnToEdit("");
459+
460+
// Create audit log for column update
461+
if (oldColumn) {
462+
await createAuditLog(supabase, {
463+
page_id: page_id,
464+
actor_id: user.id,
465+
action: `Updated Roadmap Column: ${newColumnName}`,
466+
changes: {
467+
old: oldColumn,
468+
new: { ...oldColumn, name: newColumnName }
469+
},
470+
});
471+
}
389472
} catch (error) {
390473
console.error("Error updating column:", error);
391474
alert("Failed to update column");
@@ -420,7 +503,18 @@ export default function BoardSettings({
420503

421504
if (error) throw error;
422505

506+
const deletedColumn = boardColumns.find(col => col.id === columnId);
423507
setBoardColumns((prev) => prev.filter((col) => col.id !== columnId));
508+
509+
// Create audit log for column deletion
510+
if (deletedColumn) {
511+
await createAuditLog(supabase, {
512+
page_id: page_id,
513+
actor_id: user.id,
514+
action: `Deleted Roadmap Column: ${deletedColumn.name}`,
515+
changes: { column: deletedColumn },
516+
});
517+
}
424518
} catch (error) {
425519
console.error("Error deleting column:", error);
426520
alert("Failed to delete column");
@@ -471,6 +565,17 @@ export default function BoardSettings({
471565

472566
await Promise.all(updatePromises);
473567

568+
// Create audit log for column reordering
569+
await createAuditLog(supabase, {
570+
page_id: page_id,
571+
actor_id: user.id,
572+
action: "Reordered Roadmap Columns",
573+
changes: {
574+
old_order: boardColumns.map(col => ({ id: col.id, name: col.name, position: col.position })),
575+
new_order: newColumns.map((col, index) => ({ id: col.id, name: col.name, position: index + 1 }))
576+
},
577+
});
578+
474579
// Update local state
475580
setBoardColumns(
476581
newColumns.map((column, index) => ({

0 commit comments

Comments
 (0)