From 260d59274782f46da3d30f1531bd9e131b199555 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:48:54 +0000 Subject: [PATCH 1/4] Initial plan From 4242bac13c41e1a59509aae684a7700392d38cf6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:57:32 +0000 Subject: [PATCH 2/4] Move aggregate triggers from create to receive hook for comments and votes Co-authored-by: tom-sherman <9257001+tom-sherman@users.noreply.github.com> --- packages/frontpage/lib/data/db/comment.ts | 75 ++++++++--- packages/frontpage/lib/data/db/vote.ts | 145 ++++++++++++++++++---- 2 files changed, 184 insertions(+), 36 deletions(-) diff --git a/packages/frontpage/lib/data/db/comment.ts b/packages/frontpage/lib/data/db/comment.ts index 0c222a56..a554a111 100644 --- a/packages/frontpage/lib/data/db/comment.ts +++ b/packages/frontpage/lib/data/db/comment.ts @@ -205,15 +205,59 @@ export const updateComment = async ( rkey: string, input: UpdateCommentInput, ) => { - await db - .update(schema.Comment) - .set({ - ...input, - cid: input.cid ?? undefined, - }) - .where( - and(eq(schema.Comment.authorDid, repo), eq(schema.Comment.rkey, rkey)), - ); + // If we're updating status to "live", we need to check if it was "pending" + // and trigger aggregates + if (input.status === "live") { + await db.transaction(async (tx) => { + // Get the current comment to check its status + const [currentComment] = await tx + .select({ + id: schema.Comment.id, + postId: schema.Comment.postId, + status: schema.Comment.status, + }) + .from(schema.Comment) + .where( + and(eq(schema.Comment.authorDid, repo), eq(schema.Comment.rkey, rkey)), + ) + .limit(1); + + if (!currentComment) { + throw new Error("Comment not found"); + } + + // Update the comment + await tx + .update(schema.Comment) + .set({ + ...input, + cid: input.cid ?? undefined, + }) + .where( + and(eq(schema.Comment.authorDid, repo), eq(schema.Comment.rkey, rkey)), + ); + + // If status is changing from "pending" to "live", trigger aggregates + if (currentComment.status === "pending") { + await newCommentAggregateTrigger( + currentComment.postId, + currentComment.id, + tx, + ); + } + }); + } else { + // No status change to "live", just update normally + await db + .update(schema.Comment) + .set({ + ...input, + cid: input.cid ?? undefined, + }) + .where( + and(eq(schema.Comment.authorDid, repo), eq(schema.Comment.rkey, rkey)), + ); + } }; export async function uncached_doesCommentExist(repo: DID, rkey: string) { @@ -391,11 +435,14 @@ export async function createComment({ invariant(insertedComment, "Failed to insert comment"); - await newCommentAggregateTrigger( - insertedComment.postId, - insertedComment.id, - tx, - ); + // Only update aggregates for live comments, not pending ones + if (status === "live") { + await newCommentAggregateTrigger( + insertedComment.postId, + insertedComment.id, + tx, + ); + } return insertedComment; }); diff --git a/packages/frontpage/lib/data/db/vote.ts b/packages/frontpage/lib/data/db/vote.ts index a04da959..79c88849 100644 --- a/packages/frontpage/lib/data/db/vote.ts +++ b/packages/frontpage/lib/data/db/vote.ts @@ -103,6 +103,7 @@ export const createPostVote = async ({ rkey, cid, subject, + status, collection, }: CreateVoteInput) => { return await db.transaction(async (tx) => { @@ -137,13 +138,16 @@ export const createPostVote = async ({ rkey, collection, }) - .returning({ id: schema.PostVote.id }); + .returning({ id: schema.PostVote.id, postId: schema.PostVote.postId }); if (!insertedVote) { throw new Error("Failed to insert vote"); } - await newPostVoteAggregateTrigger(post.id, tx); + // Only update aggregates for live votes, not pending ones + if (status === "live") { + await newPostVoteAggregateTrigger(insertedVote.postId, tx); + } return { id: insertedVote?.id }; }); @@ -154,6 +158,7 @@ export async function createCommentVote({ rkey, cid, subject, + status, collection, }: CreateVoteInput) { return await db.transaction(async (tx) => { @@ -185,13 +190,16 @@ export async function createCommentVote({ rkey, collection, }) - .returning({ id: schema.CommentVote.id }); + .returning({ id: schema.CommentVote.id, commentId: schema.CommentVote.commentId }); if (!insertedVote) { throw new Error("Failed to insert vote"); } - await newCommentVoteAggregateTrigger(comment.postId, comment.id, tx); + // Only update aggregates for live votes, not pending ones + if (status === "live") { + await newCommentVoteAggregateTrigger(comment.postId, insertedVote.commentId, tx); + } return { id: insertedVote?.id }; }); @@ -207,15 +215,57 @@ type UpdatePostVoteInput = Partial< export const updatePostVote = async (input: UpdatePostVoteInput) => { const { rkey, authorDid, ...updateFields } = input; - return await db - .update(schema.PostVote) - .set(updateFields) - .where( - and( - eq(schema.PostVote.rkey, rkey), - eq(schema.PostVote.authorDid, authorDid), - ), - ); + // If we're updating status to "live", we need to check if it was "pending" + // and trigger aggregates + if (updateFields.status === "live") { + await db.transaction(async (tx) => { + // Get the current vote to check its status and postId + const [currentVote] = await tx + .select({ + postId: schema.PostVote.postId, + status: schema.PostVote.status, + }) + .from(schema.PostVote) + .where( + and( + eq(schema.PostVote.rkey, rkey), + eq(schema.PostVote.authorDid, authorDid), + ), + ) + .limit(1); + + if (!currentVote) { + throw new Error("Post vote not found"); + } + + // Update the vote + await tx + .update(schema.PostVote) + .set(updateFields) + .where( + and( + eq(schema.PostVote.rkey, rkey), + eq(schema.PostVote.authorDid, authorDid), + ), + ); + + // If status is changing from "pending" to "live", trigger aggregates + if (currentVote.status === "pending") { + await newPostVoteAggregateTrigger(currentVote.postId, tx); + } + }); + } else { + // No status change to "live", just update normally + return await db + .update(schema.PostVote) + .set(updateFields) + .where( + and( + eq(schema.PostVote.rkey, rkey), + eq(schema.PostVote.authorDid, authorDid), + ), + ); + } }; type UpdateCommentVoteInput = Partial< @@ -228,15 +278,66 @@ type UpdateCommentVoteInput = Partial< export const updateCommentVote = async (input: UpdateCommentVoteInput) => { const { rkey, authorDid, ...updateFields } = input; - return await db - .update(schema.CommentVote) - .set(updateFields) - .where( - and( - eq(schema.CommentVote.rkey, rkey), - eq(schema.CommentVote.authorDid, authorDid), - ), - ); + // If we're updating status to "live", we need to check if it was "pending" + // and trigger aggregates + if (updateFields.status === "live") { + await db.transaction(async (tx) => { + // Get the current vote to check its status, commentId, and postId + const [voteWithComment] = await tx + .select({ + commentId: schema.CommentVote.commentId, + postId: schema.Comment.postId, + status: schema.CommentVote.status, + }) + .from(schema.CommentVote) + .innerJoin( + schema.Comment, + eq(schema.CommentVote.commentId, schema.Comment.id), + ) + .where( + and( + eq(schema.CommentVote.rkey, rkey), + eq(schema.CommentVote.authorDid, authorDid), + ), + ) + .limit(1); + + if (!voteWithComment) { + throw new Error("Comment vote not found"); + } + + // Update the vote + await tx + .update(schema.CommentVote) + .set(updateFields) + .where( + and( + eq(schema.CommentVote.rkey, rkey), + eq(schema.CommentVote.authorDid, authorDid), + ), + ); + + // If status is changing from "pending" to "live", trigger aggregates + if (voteWithComment.status === "pending") { + await newCommentVoteAggregateTrigger( + voteWithComment.postId, + voteWithComment.commentId, + tx, + ); + } + }); + } else { + // No status change to "live", just update normally + return await db + .update(schema.CommentVote) + .set(updateFields) + .where( + and( + eq(schema.CommentVote.rkey, rkey), + eq(schema.CommentVote.authorDid, authorDid), + ), + ); + } }; // Try deleting from both tables. In reality only one will have a record. From f908fb58af5d6d796bf9567823ce0894422c9e71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:59:21 +0000 Subject: [PATCH 3/4] Fix code review feedback: use existing variables for consistency Co-authored-by: tom-sherman <9257001+tom-sherman@users.noreply.github.com> --- packages/frontpage/lib/data/db/vote.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/frontpage/lib/data/db/vote.ts b/packages/frontpage/lib/data/db/vote.ts index 79c88849..621d23b9 100644 --- a/packages/frontpage/lib/data/db/vote.ts +++ b/packages/frontpage/lib/data/db/vote.ts @@ -138,7 +138,7 @@ export const createPostVote = async ({ rkey, collection, }) - .returning({ id: schema.PostVote.id, postId: schema.PostVote.postId }); + .returning({ id: schema.PostVote.id }); if (!insertedVote) { throw new Error("Failed to insert vote"); @@ -146,7 +146,7 @@ export const createPostVote = async ({ // Only update aggregates for live votes, not pending ones if (status === "live") { - await newPostVoteAggregateTrigger(insertedVote.postId, tx); + await newPostVoteAggregateTrigger(post.id, tx); } return { id: insertedVote?.id }; @@ -190,7 +190,7 @@ export async function createCommentVote({ rkey, collection, }) - .returning({ id: schema.CommentVote.id, commentId: schema.CommentVote.commentId }); + .returning({ id: schema.CommentVote.id }); if (!insertedVote) { throw new Error("Failed to insert vote"); @@ -198,7 +198,7 @@ export async function createCommentVote({ // Only update aggregates for live votes, not pending ones if (status === "live") { - await newCommentVoteAggregateTrigger(comment.postId, insertedVote.commentId, tx); + await newCommentVoteAggregateTrigger(comment.postId, comment.id, tx); } return { id: insertedVote?.id }; From 3a09d5900807a369904151491d5ef6d09d759415 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:01:10 +0000 Subject: [PATCH 4/4] Fix prettier formatting Co-authored-by: tom-sherman <9257001+tom-sherman@users.noreply.github.com> --- packages/frontpage/lib/data/db/comment.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/frontpage/lib/data/db/comment.ts b/packages/frontpage/lib/data/db/comment.ts index a554a111..a15f033b 100644 --- a/packages/frontpage/lib/data/db/comment.ts +++ b/packages/frontpage/lib/data/db/comment.ts @@ -218,7 +218,10 @@ export const updateComment = async ( }) .from(schema.Comment) .where( - and(eq(schema.Comment.authorDid, repo), eq(schema.Comment.rkey, rkey)), + and( + eq(schema.Comment.authorDid, repo), + eq(schema.Comment.rkey, rkey), + ), ) .limit(1); @@ -234,7 +237,10 @@ export const updateComment = async ( cid: input.cid ?? undefined, }) .where( - and(eq(schema.Comment.authorDid, repo), eq(schema.Comment.rkey, rkey)), + and( + eq(schema.Comment.authorDid, repo), + eq(schema.Comment.rkey, rkey), + ), ); // If status is changing from "pending" to "live", trigger aggregates