Skip to content

Commit

Permalink
Merge pull request #10362 from hicommonwealth/rotorsoft/10356-enhance…
Browse files Browse the repository at this point in the history
…-get-comments-api

Add address to results
  • Loading branch information
Rotorsoft authored Dec 24, 2024
2 parents 8a1dd9e + c0d6eb4 commit 64094d5
Show file tree
Hide file tree
Showing 4 changed files with 195 additions and 57 deletions.
148 changes: 98 additions & 50 deletions libs/model/src/comment/GetComments.query.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,120 @@
import { type Query } from '@hicommonwealth/core';
import * as schemas from '@hicommonwealth/schemas';
import { CommentsView } from '@hicommonwealth/schemas';
import { QueryTypes } from 'sequelize';
import { z } from 'zod';
import { models } from '../database';
import { removeUndefined, sanitizeDeletedComment } from '../utils/index';
import { formatSequelizePagination } from '../utils/paginationUtils';
import { sanitizeDeletedComment } from '../utils/index';

export function GetComments(): Query<typeof schemas.GetComments> {
return {
...schemas.GetComments,
auth: [],
secure: false,
body: async ({ payload }) => {
const { thread_id, comment_id, include_user, include_reactions } =
const { thread_id, comment_id, include_reactions, limit, cursor } =
payload;

const includeArray = [];
if (include_user) {
includeArray.push({
model: models.Address,
include: [
{
model: models.User,
as: 'User',
required: true,
attributes: ['id', 'profile'],
},
],
});
}

if (include_reactions) {
includeArray.push({
model: models.Reaction,
as: 'reactions',
include: [
{
model: models.Address,
as: 'Address',
required: true,
attributes: ['address', 'last_active'],
include: [
{
model: models.User,
as: 'User',
required: true,
attributes: ['id', 'profile'],
},
],
},
],
});
}
const sql = `
SELECT
C.id,
C.body,
C.created_at,
C.updated_at,
C.deleted_at,
C.marked_as_spam_at,
C.reaction_count,
CA.address,
CA.last_active,
CU.id AS "user_id",
CU.profile->>'name' AS "profile_name",
CU.profile->>'avatar_url' AS "avatar_url",
${
include_reactions
? `
json_agg(json_strip_nulls(json_build_object(
'id', R.id,
'address_id', R.address_id,
'reaction', R.reaction,
'created_at', R.created_at::text,
'updated_at', R.updated_at::text,
'calculated_voting_weight', R.calculated_voting_weight::text,
'address', RA.address,
'last_active', RA.last_active::text,
'profile_name', RU.profile->>'name',
'avatar_url', RU.profile->>'avatar_url'
))) AS "reactions",
`
: ''
}
COUNT(*) OVER() AS total_count
FROM
"Comments" AS C
JOIN "Addresses" AS CA ON C."address_id" = CA."id"
JOIN "Users" AS CU ON CA."user_id" = CU."id"
${
include_reactions
? `
LEFT JOIN "Reactions" AS R ON C."id" = R."comment_id"
LEFT JOIN "Addresses" AS RA ON R."address_id" = RA."id"
LEFT JOIN "Users" AS RU ON RA."user_id" = RU."id"
`
: ''
}
WHERE
C."thread_id" = :thread_id
${comment_id ? ' AND C."id" = :comment_id' : ''}
${
include_reactions
? `
GROUP BY
C.id,
C.created_at,
C.updated_at,
C.deleted_at,
C.marked_as_spam_at,
CA.address,
CA.last_active,
CU.id,
CU.profile->>'name',
CU.profile->>'avatar_url'
`
: ''
}
ORDER BY
C."created_at"
LIMIT :limit OFFSET :offset;
`;

const { count, rows: comments } = await models.Comment.findAndCountAll({
where: removeUndefined({ thread_id, id: comment_id }),
include: includeArray,
...formatSequelizePagination(payload),
paranoid: false,
const comments = await models.sequelize.query<
z.infer<typeof CommentsView> & {
total_count: number;
}
>(sql, {
replacements: {
thread_id,
comment_id,
limit,
offset: (cursor - 1) * limit,
},
type: QueryTypes.SELECT,
});

const sanitizedComments = comments.map((c) => {
const data = c.toJSON();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { total_count, ...rest } = c;
return {
...sanitizeDeletedComment(data),
last_edited: data.updated_at,
};
...sanitizeDeletedComment(
rest as unknown as z.infer<typeof schemas.Comment>,
),
} as unknown as z.infer<typeof CommentsView>;
});

return schemas.buildPaginatedResponse(sanitizedComments, count, payload);
return schemas.buildPaginatedResponse(
sanitizedComments,
comments?.length ? comments!.at(0)!.total_count : 0,
payload,
);
},
};
}
7 changes: 4 additions & 3 deletions libs/model/src/utils/sanitizeDeletedComment.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CommentAttributes } from '../models/comment';
import { Comment } from '@hicommonwealth/schemas';
import { z } from 'zod';

export function sanitizeDeletedComment(
comment: CommentAttributes,
): CommentAttributes {
comment: z.infer<typeof Comment>,
): z.infer<typeof Comment> {
if (!comment.deleted_at) {
return comment;
}
Expand Down
87 changes: 87 additions & 0 deletions libs/model/test/thread/thread-lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
CreateCommentErrors,
CreateCommentReaction,
DeleteComment,
GetComments,
MAX_COMMENT_DEPTH,
UpdateComment,
} from '../../src/comment';
Expand Down Expand Up @@ -845,6 +846,92 @@ describe('Thread lifecycle', () => {
}),
).rejects.toThrowError(InvalidActor);
});

test('should get comments with reactions', async () => {
await command(CreateComment(), {
actor: actors.admin,
payload: {
parent_msg_id: thread!.canvas_msg_id,
thread_id: thread.id!,
body: 'hello',
},
});
await command(CreateComment(), {
actor: actors.member,
payload: {
parent_msg_id: thread!.canvas_msg_id,
thread_id: thread.id!,
body: 'world',
},
});
const response = await query(GetComments(), {
actor: actors.member,
payload: {
limit: 50,
cursor: 1,
thread_id: thread.id!,
include_reactions: true,
},
});
expect(response!.results.length).to.equal(15);
const last = response!.results.at(-1)!;
const stl = response!.results.at(-2)!;
expect(last!.address).to.equal(actors.member.address);
expect(last!.user_id).to.equal(actors.member.user.id);
expect(last!.body).to.equal('world');
expect(stl!.address).to.equal(actors.admin.address);
expect(stl!.user_id).to.equal(actors.admin.user.id);
expect(stl!.body).to.equal('hello');

// get second comment with reactions
const response2 = await query(GetComments(), {
actor: actors.member,
payload: {
limit: 50,
cursor: 1,
thread_id: thread.id!,
comment_id: response?.results.at(1)!.id,
include_reactions: true,
},
});
const second = response2!.results.at(0)!;
expect(second!.reactions!.length).to.equal(1);
});

test('should get comments without reactions', async () => {
const response = await query(GetComments(), {
actor: actors.member,
payload: {
limit: 50,
cursor: 1,
thread_id: thread.id!,
include_reactions: false,
},
});
expect(response!.results.length).to.equal(15);
const last = response!.results.at(-1)!;
const stl = response!.results.at(-2)!;
expect(last!.address).to.equal(actors.member.address);
expect(last!.user_id).to.equal(actors.member.user.id);
expect(last!.body).to.equal('world');
expect(stl!.address).to.equal(actors.admin.address);
expect(stl!.user_id).to.equal(actors.admin.user.id);
expect(stl!.body).to.equal('hello');

// get second comment without reactions
const response2 = await query(GetComments(), {
actor: actors.member,
payload: {
limit: 50,
cursor: 1,
thread_id: thread.id!,
comment_id: response?.results.at(1)!.id,
include_reactions: false,
},
});
const second = response2!.results.at(0)!;
expect(second!.reactions).to.be.undefined;
});
});

describe('thread reaction', () => {
Expand Down
10 changes: 6 additions & 4 deletions libs/schemas/src/queries/comment.schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import z from 'zod';
import { Comment } from '../entities';
import { PG_INT, zBoolean } from '../utils';
import { PaginatedResultSchema, PaginationParamsSchema } from './pagination';
import { CommentView, ReactionView } from './thread.schemas';

export const SearchComments = {
input: z.object({
Expand All @@ -17,16 +18,17 @@ export const SearchComments = {
}),
};

export const CommentsView = CommentView.extend({
reactions: z.array(ReactionView).nullish(),
});

export const GetComments = {
input: PaginationParamsSchema.extend({
thread_id: PG_INT,
comment_id: PG_INT.optional(),
include_user: zBoolean.default(false),
include_reactions: zBoolean.default(false),
}),
output: PaginatedResultSchema.extend({
results: Comment.extend({
last_edited: z.coerce.date().optional(),
}).array(),
results: z.array(CommentsView),
}),
};

0 comments on commit 64094d5

Please sign in to comment.