Skip to content

Feat/comments#160

Merged
Angus-Paillaugue merged 5 commits into
mainfrom
feat/comments
Jan 7, 2026
Merged

Feat/comments#160
Angus-Paillaugue merged 5 commits into
mainfrom
feat/comments

Conversation

@Angus-Paillaugue
Copy link
Copy Markdown
Member

No description provided.

 - Can create a comment for a post
 - Can reply to a comment
 - Fixes to the previous commit
 - Updated the feed algorithm to take comments into account when ranking posts
Copilot AI review requested due to automatic review settings January 7, 2026 15:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

{#if newComment.commentId}
<Dialog.Title
>{i18n.t('social.post.comments.reply.title', {
username: getCommentFromId(newComment.commentId)?.user.username,
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getCommentFromId function can return null, but this is not handled in the dialog title. If the commentId is set to an invalid value (e.g., a comment that was deleted), this will cause a runtime error when trying to access .user.username on null. Consider adding a fallback or validation, such as using optional chaining: getCommentFromId(newComment.commentId)?.user?.username ?? 'Unknown User'.

Suggested change
username: getCommentFromId(newComment.commentId)?.user.username,
username: getCommentFromId(newComment.commentId)?.user?.username ?? 'Unknown User',

Copilot uses AI. Check for mistakes.
Comment thread src/lib/server/db/comment.ts Outdated
Comment on lines +78 to +88
static async getCommentsForPost(postId: Comment['postId']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
'SELECT id FROM comment WHERE publication_id = $1 AND comment_id IS NULL ORDER BY created_at ASC',
[postId]
);
const comments: Comment[] = [];
for (const commentId of res.rows) {
const comment = await this.getComment(commentId.id);
if (comment) comments.push(comment);
}
return comments;
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation has an N+1 query problem. For each comment ID retrieved, a separate database query is made in getComment(). This will result in poor performance when there are many comments. Consider fetching all comment data in a single query and then building the tree structure in memory, or use a recursive CTE to fetch the entire comment tree in one query.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/server/db/comment.ts Outdated
Comment on lines +65 to +88
static async getRepliesToComment(commentId: Comment['id']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
'SELECT id FROM comment WHERE comment_id = $1 ORDER BY created_at ASC',
[commentId]
);
const replies: Comment[] = [];
for (const commentId of res.rows) {
const comment = await this.getComment(commentId.id);
if (comment) replies.push(comment);
}
return replies;
}

static async getCommentsForPost(postId: Comment['postId']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
'SELECT id FROM comment WHERE publication_id = $1 AND comment_id IS NULL ORDER BY created_at ASC',
[postId]
);
const comments: Comment[] = [];
for (const commentId of res.rows) {
const comment = await this.getComment(commentId.id);
if (comment) comments.push(comment);
}
return comments;
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implementation has an N+1 query problem. For each reply ID retrieved, a separate database query is made in getComment(). This will result in poor performance when there are many replies. Consider fetching all replies in a single query and then recursively building the tree structure, or use a recursive CTE.

Suggested change
static async getRepliesToComment(commentId: Comment['id']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
'SELECT id FROM comment WHERE comment_id = $1 ORDER BY created_at ASC',
[commentId]
);
const replies: Comment[] = [];
for (const commentId of res.rows) {
const comment = await this.getComment(commentId.id);
if (comment) replies.push(comment);
}
return replies;
}
static async getCommentsForPost(postId: Comment['postId']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
'SELECT id FROM comment WHERE publication_id = $1 AND comment_id IS NULL ORDER BY created_at ASC',
[postId]
);
const comments: Comment[] = [];
for (const commentId of res.rows) {
const comment = await this.getComment(commentId.id);
if (comment) comments.push(comment);
}
return comments;
private static async buildCommentTree(
rows: CommentTable[],
rootParentId: Comment['id'] | null
): Promise<Comment[]> {
const childrenByParent = new Map<Comment['id'] | null, CommentTable[]>();
for (const row of rows) {
const parentId = (row.comment_id as Comment['id'] | null) ?? null;
const existing = childrenByParent.get(parentId);
if (existing) {
existing.push(row);
} else {
childrenByParent.set(parentId, [row]);
}
}
const buildForParent = async (parentId: Comment['id'] | null): Promise<Comment[]> => {
const children = childrenByParent.get(parentId) ?? [];
const result: Comment[] = [];
for (const childRow of children) {
const replies = await buildForParent(childRow.id);
const associatedUser = await UserDAO.getUserById(childRow.user_id);
const comment = this.convertToComment(childRow, associatedUser, replies);
result.push(comment);
}
return result;
};
return buildForParent(rootParentId);
}
static async getRepliesToComment(commentId: Comment['id']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
`
WITH RECURSIVE comment_tree AS (
SELECT *
FROM comment
WHERE comment_id = $1
UNION ALL
SELECT c.*
FROM comment c
INNER JOIN comment_tree ct ON c.comment_id = ct.id
)
SELECT *
FROM comment_tree
ORDER BY created_at ASC
`,
[commentId]
);
return this.buildCommentTree(res.rows, commentId);
}
static async getCommentsForPost(postId: Comment['postId']): Promise<Comment[]> {
const res = await pool.query<CommentTable>(
`
WITH RECURSIVE comment_tree AS (
SELECT *
FROM comment
WHERE publication_id = $1 AND comment_id IS NULL
UNION ALL
SELECT c.*
FROM comment c
INNER JOIN comment_tree ct ON c.comment_id = ct.id
)
SELECT *
FROM comment_tree
ORDER BY created_at ASC
`,
[postId]
);
return this.buildCommentTree(res.rows, null);

Copilot uses AI. Check for mistakes.

newComment: async ({ locals, params, request }) => {
const schema = z.object({
content: z.string().min(1, 'errors.social.post.comments.addComment.empty'),
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema validation allows empty strings after trimming. A user could submit a comment with only whitespace characters that passes the min(1) validation but becomes empty after trim(). Consider adding .trim() to the zod schema using z.string().trim().min(1) to ensure validation happens after trimming.

Suggested change
content: z.string().min(1, 'errors.social.post.comments.addComment.empty'),
content: z.string().trim().min(1, 'errors.social.post.comments.addComment.empty'),

Copilot uses AI. Check for mistakes.
@Angus-Paillaugue Angus-Paillaugue merged commit f1d74dc into main Jan 7, 2026
2 checks passed
@Angus-Paillaugue Angus-Paillaugue deleted the feat/comments branch January 7, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants