feat: paginate and merge frequent activities (#274)

* feat: paginate and merge frequent activities

* feat: due date activities

* fix: requested changes fixes

* refactor: slight reshuffle and improve types

* chore: translations

---------

Co-authored-by: Henry <henry_ball@hotmail.co.uk>
This commit is contained in:
Ridham Khandar
2025-12-16 14:37:08 +05:30
committed by GitHub
parent 3cb40d0f9a
commit 57186b6b4d
40 changed files with 1515 additions and 884 deletions

View File

@@ -9,6 +9,7 @@ import * as listRepo from "@kan/db/repository/list.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
import { mergeActivities } from "../utils/activities";
import { assertUserInWorkspace } from "../utils/auth";
import { generateDownloadUrl } from "../utils/s3";
@@ -670,6 +671,81 @@ export const cardRouter = createTRPCRouter({
return { ...result, attachments: [] };
}),
getActivities: publicProcedure
.meta({
openapi: {
summary: "Get paginated card activities",
method: "GET",
path: "/cards/{cardPublicId}/activities",
description:
"Retrieves paginated activities for a card with merged frequent changes",
tags: ["Cards"],
},
})
.input(
z.object({
cardPublicId: z.string().min(12),
limit: z.number().min(1).max(100).optional().default(10),
cursor: z.string().datetime().optional(), // ISO datetime string
}),
)
.output(
z.object({
activities: z.array(
z.custom<
NonNullable<
Awaited<
ReturnType<typeof cardActivityRepo.getPaginatedActivities>
>
>["activities"][number]
>(),
),
hasMore: z.boolean(),
nextCursor: z.string().datetime().nullable(),
}),
)
.query(async ({ ctx, input }) => {
const card = await cardRepo.getWorkspaceAndCardIdByCardPublicId(
ctx.db,
input.cardPublicId,
);
if (!card)
throw new TRPCError({
message: `Card with public ID ${input.cardPublicId} not found`,
code: "NOT_FOUND",
});
if (card.workspaceVisibility === "private") {
const userId = ctx.user?.id;
if (!userId)
throw new TRPCError({
message: `User not authenticated`,
code: "UNAUTHORIZED",
});
await assertUserInWorkspace(ctx.db, userId, card.workspaceId);
}
const cursor = input.cursor ? new Date(input.cursor) : undefined;
const result = await cardActivityRepo.getPaginatedActivities(
ctx.db,
card.id,
{
limit: input.limit,
cursor,
},
);
const mergedActivities = mergeActivities(result.activities);
return {
activities: mergedActivities,
hasMore: result.hasMore,
nextCursor: result.nextCursor?.toISOString() ?? null,
};
}),
update: protectedProcedure
.meta({
openapi: {