diff --git a/apps/web/src/views/card/components/ActivityList.tsx b/apps/web/src/views/card/components/ActivityList.tsx index 3cfc397a1..5def8b42c 100644 --- a/apps/web/src/views/card/components/ActivityList.tsx +++ b/apps/web/src/views/card/components/ActivityList.tsx @@ -23,11 +23,13 @@ import type { } from "@kan/api/types"; import { authClient } from "@kan/auth/client"; +import type { WorkspaceMember } from "~/components/Editor"; import Avatar from "~/components/Avatar"; import { useLocalisation } from "~/hooks/useLocalisation"; import { api } from "~/utils/api"; import { getAvatarUrl } from "~/utils/helpers"; import Comment from "./Comment"; +import NewCommentForm from "./NewCommentForm"; type ActivityType = NonNullable["activities"][number]["type"]; @@ -373,11 +375,19 @@ const ActivityList = ({ isLoading: cardIsLoading, isAdmin, isViewOnly, + onReplyToComment, + replyToComment, + onCancelReply, + workspaceMembers, }: { cardPublicId: string; isLoading: boolean; isAdmin?: boolean; isViewOnly?: boolean; + onReplyToComment?: (args: { publicId: string; name: string }) => void; + replyToComment?: { publicId: string; name: string } | null; + onCancelReply?: () => void; + workspaceMembers?: WorkspaceMember[]; }) => { const { dateLocale } = useLocalisation(); const { data: sessionData } = authClient.useSession(); @@ -386,6 +396,7 @@ const ActivityList = ({ GetCardActivitiesOutput["activities"] >([]); const [hasMore, setHasMore] = useState(true); + const [nextCursor, setNextCursor] = useState(null); const [isLoadingMore, setIsLoadingMore] = useState(false); const isFullyExpandedRef = useRef(false); @@ -412,21 +423,18 @@ const ActivityList = ({ if (isFullyExpandedRef.current && firstPageData.hasMore) { setAllActivities(firstPageData.activities); setHasMore(firstPageData.hasMore); + setNextCursor(firstPageData.nextCursor); const fetchAllRemaining = async () => { let currentActivities = [...firstPageData.activities]; let currentHasMore = firstPageData.hasMore; + let currentCursor = firstPageData.nextCursor; - while (currentHasMore) { - const lastActivity = - currentActivities[currentActivities.length - 1]; - if (!lastActivity) break; - - const nextCursor = new Date(lastActivity.createdAt).toISOString(); + while (currentHasMore && currentCursor) { const nextPage = await utils.card.getActivities.fetch({ cardPublicId, limit: ACTIVITIES_PAGE_SIZE, - cursor: nextCursor, + cursor: currentCursor, }); const existingIds = new Set( @@ -437,16 +445,19 @@ const ActivityList = ({ ); currentActivities = [...currentActivities, ...newActivities]; currentHasMore = nextPage.hasMore; + currentCursor = nextPage.nextCursor; } setAllActivities(currentActivities); setHasMore(false); + setNextCursor(null); }; void fetchAllRemaining(); } else { setAllActivities(firstPageData.activities); setHasMore(firstPageData.hasMore); + setNextCursor(firstPageData.nextCursor); if (!firstPageData.hasMore) { isFullyExpandedRef.current = true; @@ -456,14 +467,10 @@ const ActivityList = ({ }, [firstPageData, dataUpdatedAt, cardPublicId, utils.card.getActivities]); const handleLoadMore = async () => { - if (isLoadingMore || !hasMore || allActivities.length === 0) return; - - const lastActivity = allActivities[allActivities.length - 1]; - if (!lastActivity) return; + if (isLoadingMore || !hasMore || !nextCursor) return; setIsLoadingMore(true); try { - const nextCursor = new Date(lastActivity.createdAt).toISOString(); const nextPage = await utils.card.getActivities.fetch({ cardPublicId, limit: ACTIVITIES_PAGE_SIZE, @@ -476,6 +483,7 @@ const ActivityList = ({ ); setAllActivities((prev) => [...prev, ...newActivities]); setHasMore(nextPage.hasMore); + setNextCursor(nextPage.nextCursor); if (!nextPage.hasMore) { isFullyExpandedRef.current = true; @@ -489,6 +497,136 @@ const ActivityList = ({ const isLoading = cardIsLoading || (isFetchingFirst && allActivities.length === 0); + const commentActivityByCommentPublicId = new Map< + string, + GetCardActivitiesOutput["activities"][number] + >(); + const commentParentByPublicId = new Map(); + const commentChildrenByParentPublicId = new Map(); + + const getCreatedAtMs = (createdAt: unknown) => { + try { + return new Date(createdAt as Date).getTime(); + } catch { + return 0; + } + }; + + for (const activity of allActivities) { + if (activity.type !== "card.updated.comment.added") continue; + const commentPublicId = activity.comment?.publicId; + if (!commentPublicId) continue; + + const parentPublicId = activity.comment?.parentCommentPublicId ?? null; + commentActivityByCommentPublicId.set(commentPublicId, activity); + commentParentByPublicId.set(commentPublicId, parentPublicId); + + if (parentPublicId) { + const children = + commentChildrenByParentPublicId.get(parentPublicId) ?? []; + children.push(commentPublicId); + commentChildrenByParentPublicId.set(parentPublicId, children); + } + } + + for (const [parentPublicId, childIds] of commentChildrenByParentPublicId) { + childIds.sort((a, b) => { + const aActivity = commentActivityByCommentPublicId.get(a); + const bActivity = commentActivityByCommentPublicId.get(b); + return ( + getCreatedAtMs(aActivity?.createdAt) - + getCreatedAtMs(bActivity?.createdAt) + ); + }); + commentChildrenByParentPublicId.set(parentPublicId, childIds); + } + + const INDENT_CLASSES = [ + "", + "ml-8", + "ml-14", + "ml-20", + "ml-24", + "ml-28", + "ml-32", + ] as const; + const getDepthClass = (depth: number) => { + const normalizedDepth = Math.max(0, depth); + return INDENT_CLASSES[Math.min(normalizedDepth, INDENT_CLASSES.length - 1)]; + }; + + const renderCommentThread = ( + commentPublicId: string, + depth: number, + ancestry: Set, + rendered: Set, + ): React.ReactNode => { + if (rendered.has(commentPublicId)) return null; + if (ancestry.has(commentPublicId)) return null; + + const activity = commentActivityByCommentPublicId.get(commentPublicId); + if (!activity || activity.type !== "card.updated.comment.added") + return null; + + rendered.add(commentPublicId); + + const nextAncestry = new Set(ancestry); + nextAncestry.add(commentPublicId); + + const children = commentChildrenByParentPublicId.get(commentPublicId) ?? []; + const showReplyForm = + !isViewOnly && + !!workspaceMembers && + !!replyToComment && + replyToComment.publicId === commentPublicId; + + return ( +
+ + + {showReplyForm && ( +
+ +
+ )} + + {children.map((childCommentPublicId) => + renderCommentThread( + childCommentPublicId, + depth + 1, + nextAncestry, + rendered, + ), + )} +
+ ); + }; + return (
{allActivities.map((activity, index) => { @@ -507,28 +645,26 @@ const ActivityList = ({ dateLocale: dateLocale, mergedLabels: (activity as ActivityWithMergedLabels).mergedLabels, attachmentName: - (activity as ActivityWithMergedLabels).attachment?.originalFilename ?? - null, + (activity as ActivityWithMergedLabels).attachment + ?.originalFilename ?? null, }); - if (activity.type === "card.updated.comment.added") - return ( - - ); + if (activity.type === "card.updated.comment.added") { + const commentPublicId = activity.comment?.publicId; + if (!commentPublicId) return null; + + const parentPublicId = commentParentByPublicId.get(commentPublicId); + const parentIsLoaded = + !!parentPublicId && + commentActivityByCommentPublicId.has(parentPublicId); + + if (parentIsLoaded) { + return null; + } + + const rendered = new Set(); + return renderCommentThread(commentPublicId, 0, new Set(), rendered); + } if (!activityText) return null; @@ -542,7 +678,9 @@ const ActivityList = ({ size="sm" name={activity.user?.name ?? ""} email={activity.user?.email ?? ""} - imageUrl={getAvatarUrl(activity.user?.image ?? null) || undefined} + imageUrl={ + getAvatarUrl(activity.user?.image ?? null) || undefined + } icon={getActivityIcon( activity.type, activity.fromList?.index, diff --git a/apps/web/src/views/card/components/Comment.tsx b/apps/web/src/views/card/components/Comment.tsx index 6653c563e..93b949d87 100644 --- a/apps/web/src/views/card/components/Comment.tsx +++ b/apps/web/src/views/card/components/Comment.tsx @@ -2,13 +2,18 @@ import { t } from "@lingui/core/macro"; import { formatDistanceToNow } from "date-fns"; import { useState } from "react"; import { useForm } from "react-hook-form"; -import { HiEllipsisHorizontal, HiPencil, HiTrash } from "react-icons/hi2"; +import { + HiEllipsisHorizontal, + HiOutlineArrowUp, + HiPencil, + HiTrash, +} from "react-icons/hi2"; +import type { WorkspaceMember } from "~/components/Editor"; import Avatar from "~/components/Avatar"; import Button from "~/components/Button"; -import Editor from "~/components/Editor"; -import type { WorkspaceMember } from "~/components/Editor"; import Dropdown from "~/components/Dropdown"; +import Editor from "~/components/Editor"; import { usePermissions } from "~/hooks/usePermissions"; import { useModal } from "~/providers/modal"; import { usePopup } from "~/providers/popup"; @@ -32,6 +37,8 @@ const Comment = ({ isAuthor, isEdited = false, isViewOnly = false, + depth = 0, + onReply, }: { publicId: string | undefined; cardPublicId: string; @@ -44,12 +51,15 @@ const Comment = ({ isAuthor: boolean; isEdited: boolean; isViewOnly: boolean; + depth?: number; + onReply?: (args: { publicId: string; name: string }) => void; }) => { const [isEditing, setIsEditing] = useState(false); const utils = api.useUtils(); const { showPopup } = usePopup(); const { openModal } = useModal(); - const { canEditComment, canDeleteComment } = usePermissions(); + const { canEditComment, canDeleteComment, canCreateComment } = + usePermissions(); const { handleSubmit, setValue, watch } = useForm({ defaultValues: { comment, @@ -82,6 +92,19 @@ const Comment = ({ if (!publicId) return null; + const INDENT_CLASSES = [ + "", + "ml-8", + "ml-14", + "ml-20", + "ml-24", + "ml-28", + "ml-32", + ] as const; + const normalizedDepth = Math.max(0, depth ?? 0); + const depthClass = + INDENT_CLASSES[Math.min(normalizedDepth, INDENT_CLASSES.length - 1)]; + const updateCommentMutation = api.card.updateComment.useMutation({ onSuccess: async () => { await invalidateCard(utils, cardPublicId); @@ -114,7 +137,7 @@ const Comment = ({ }, ] : []), - ...((isAuthor || canDeleteComment) + ...(isAuthor || canDeleteComment ? [ { label: t`Delete comment`, @@ -128,7 +151,7 @@ const Comment = ({ return (
@@ -173,6 +196,23 @@ const Comment = ({ enableYouTubeEmbed={false} disableHeadings={true} /> + + {canCreateComment && !isViewOnly && onReply && ( +
+ +
+ )}
) : (
@@ -186,7 +226,7 @@ const Comment = ({ disableHeadings={true} />
-
+
+ )} +
+ )} setValue("comment", value)} diff --git a/apps/web/src/views/card/index.tsx b/apps/web/src/views/card/index.tsx index 7eacfc438..de854de57 100644 --- a/apps/web/src/views/card/index.tsx +++ b/apps/web/src/views/card/index.tsx @@ -180,6 +180,10 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) { const [activeChecklistForm, setActiveChecklistForm] = useState( null, ); + const [replyToComment, setReplyToComment] = useState<{ + publicId: string; + name: string; + } | null>(null); const cardId = Array.isArray(router.query.cardId) ? router.query.cardId[0] @@ -459,9 +463,13 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) { cardPublicId={cardId} isLoading={!card} isAdmin={workspace.role === "admin"} + onReplyToComment={(args) => setReplyToComment(args)} + replyToComment={replyToComment} + onCancelReply={() => setReplyToComment(null)} + workspaceMembers={editorWorkspaceMembers} />
- {!isTemplate && ( + {!isTemplate && !replyToComment && (
>>()) @@ -235,12 +240,37 @@ export const cardRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertPermission(ctx.db, userId, card.workspaceId, "comment:create"); + await assertPermission( + ctx.db, + userId, + card.workspaceId, + "comment:create", + ); + + if (input.parentCommentPublicId) { + const parentComment = await cardCommentRepo.getByPublicId( + ctx.db, + input.parentCommentPublicId, + ); + + if (!parentComment || parentComment.deletedAt) + throw new TRPCError({ + message: `Parent comment with public ID ${input.parentCommentPublicId} not found`, + code: "NOT_FOUND", + }); + + if (parentComment.cardId !== card.id) + throw new TRPCError({ + message: `Parent comment does not belong to the specified card`, + code: "BAD_REQUEST", + }); + } const newComment = await cardCommentRepo.create(ctx.db, { comment: input.comment, createdBy: userId, cardId: card.id, + parentCommentPublicId: input.parentCommentPublicId ?? null, }); if (!newComment?.id) diff --git a/packages/db/migrations/20260311204622_AddParentCommentToCardComments.sql b/packages/db/migrations/20260311204622_AddParentCommentToCardComments.sql new file mode 100644 index 000000000..504a17478 --- /dev/null +++ b/packages/db/migrations/20260311204622_AddParentCommentToCardComments.sql @@ -0,0 +1 @@ +ALTER TABLE "card_comments" ADD COLUMN IF NOT EXISTS "parentCommentPublicId" varchar(12); \ No newline at end of file diff --git a/packages/db/migrations/meta/20260311204622_snapshot.json b/packages/db/migrations/meta/20260311204622_snapshot.json new file mode 100644 index 000000000..6687a3678 --- /dev/null +++ b/packages/db/migrations/meta/20260311204622_snapshot.json @@ -0,0 +1,3874 @@ +{ + "id": "55b7ed99-5262-4289-8dd2-8031dedd4125", + "prevId": "c8acd64a-f67f-4119-8cf3-d98322744d50", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.apiKey": { + "name": "apiKey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "refillInterval": { + "name": "refillInterval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refillAmount": { + "name": "refillAmount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lastRefillAt": { + "name": "lastRefillAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rateLimitEnabled": { + "name": "rateLimitEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "rateLimitTimeWindow": { + "name": "rateLimitTimeWindow", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rateLimitMax": { + "name": "rateLimitMax", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "requestCount": { + "name": "requestCount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "lastRequest": { + "name": "lastRequest", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "apiKey_userId_user_id_fk": { + "name": "apiKey_userId_user_id_fk", + "tableFrom": "apiKey", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.board": { + "name": "board", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "board_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "type": { + "name": "type", + "type": "board_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'regular'" + }, + "isArchived": { + "name": "isArchived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sourceBoardId": { + "name": "sourceBoardId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "board_is_archived_idx": { + "name": "board_is_archived_idx", + "columns": [ + { + "expression": "isArchived", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_visibility_idx": { + "name": "board_visibility_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_type_idx": { + "name": "board_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_source_idx": { + "name": "board_source_idx", + "columns": [ + { + "expression": "sourceBoardId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_slug_per_workspace": { + "name": "unique_slug_per_workspace", + "columns": [ + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"board\".\"deletedAt\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_createdBy_user_id_fk": { + "name": "board_createdBy_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "board_deletedBy_user_id_fk": { + "name": "board_deletedBy_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "board_importId_import_id_fk": { + "name": "board_importId_import_id_fk", + "tableFrom": "board", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "board_workspaceId_workspace_id_fk": { + "name": "board_workspaceId_workspace_id_fk", + "tableFrom": "board", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "board_publicId_unique": { + "name": "board_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user_board_favorites": { + "name": "user_board_favorites", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_board_favorite_user_idx": { + "name": "user_board_favorite_user_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_board_favorite_board_idx": { + "name": "user_board_favorite_board_idx", + "columns": [ + { + "expression": "boardId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_board_favorites_userId_user_id_fk": { + "name": "user_board_favorites_userId_user_id_fk", + "tableFrom": "user_board_favorites", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_board_favorites_boardId_board_id_fk": { + "name": "user_board_favorites_boardId_board_id_fk", + "tableFrom": "user_board_favorites", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_board_favorites_userId_boardId_pk": { + "name": "user_board_favorites_userId_boardId_pk", + "columns": [ + "userId", + "boardId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.card_activity": { + "name": "card_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "card_activity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "fromIndex": { + "name": "fromIndex", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toIndex": { + "name": "toIndex", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fromListId": { + "name": "fromListId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "toListId": { + "name": "toListId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "labelId": { + "name": "labelId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "fromTitle": { + "name": "fromTitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toTitle": { + "name": "toTitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fromDescription": { + "name": "fromDescription", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toDescription": { + "name": "toDescription", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "commentId": { + "name": "commentId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "fromComment": { + "name": "fromComment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toComment": { + "name": "toComment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fromDueDate": { + "name": "fromDueDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "toDueDate": { + "name": "toDueDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sourceBoardId": { + "name": "sourceBoardId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "attachmentId": { + "name": "attachmentId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_activity_cardId_card_id_fk": { + "name": "card_activity_cardId_card_id_fk", + "tableFrom": "card_activity", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_fromListId_list_id_fk": { + "name": "card_activity_fromListId_list_id_fk", + "tableFrom": "card_activity", + "tableTo": "list", + "columnsFrom": [ + "fromListId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_toListId_list_id_fk": { + "name": "card_activity_toListId_list_id_fk", + "tableFrom": "card_activity", + "tableTo": "list", + "columnsFrom": [ + "toListId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_labelId_label_id_fk": { + "name": "card_activity_labelId_label_id_fk", + "tableFrom": "card_activity", + "tableTo": "label", + "columnsFrom": [ + "labelId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_workspaceMemberId_workspace_members_id_fk": { + "name": "card_activity_workspaceMemberId_workspace_members_id_fk", + "tableFrom": "card_activity", + "tableTo": "workspace_members", + "columnsFrom": [ + "workspaceMemberId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_activity_createdBy_user_id_fk": { + "name": "card_activity_createdBy_user_id_fk", + "tableFrom": "card_activity", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_activity_commentId_card_comments_id_fk": { + "name": "card_activity_commentId_card_comments_id_fk", + "tableFrom": "card_activity", + "tableTo": "card_comments", + "columnsFrom": [ + "commentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_sourceBoardId_board_id_fk": { + "name": "card_activity_sourceBoardId_board_id_fk", + "tableFrom": "card_activity", + "tableTo": "board", + "columnsFrom": [ + "sourceBoardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_activity_attachmentId_card_attachment_id_fk": { + "name": "card_activity_attachmentId_card_attachment_id_fk", + "tableFrom": "card_activity", + "tableTo": "card_attachment", + "columnsFrom": [ + "attachmentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_activity_publicId_unique": { + "name": "card_activity_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_attachment": { + "name": "card_attachment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "originalFilename": { + "name": "originalFilename", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "contentType": { + "name": "contentType", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "s3Key": { + "name": "s3Key", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_attachment_cardId_card_id_fk": { + "name": "card_attachment_cardId_card_id_fk", + "tableFrom": "card_attachment", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_attachment_createdBy_user_id_fk": { + "name": "card_attachment_createdBy_user_id_fk", + "tableFrom": "card_attachment", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_attachment_publicId_unique": { + "name": "card_attachment_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public._card_workspace_members": { + "name": "_card_workspace_members", + "schema": "", + "columns": { + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "_card_workspace_members_cardId_card_id_fk": { + "name": "_card_workspace_members_cardId_card_id_fk", + "tableFrom": "_card_workspace_members", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "_card_workspace_members_workspaceMemberId_workspace_members_id_fk": { + "name": "_card_workspace_members_workspaceMemberId_workspace_members_id_fk", + "tableFrom": "_card_workspace_members", + "tableTo": "workspace_members", + "columnsFrom": [ + "workspaceMemberId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "_card_workspace_members_cardId_workspaceMemberId_pk": { + "name": "_card_workspace_members_cardId_workspaceMemberId_pk", + "columns": [ + "cardId", + "workspaceMemberId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card": { + "name": "card", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "listId": { + "name": "listId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "dueDate": { + "name": "dueDate", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_createdBy_user_id_fk": { + "name": "card_createdBy_user_id_fk", + "tableFrom": "card", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_deletedBy_user_id_fk": { + "name": "card_deletedBy_user_id_fk", + "tableFrom": "card", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_listId_list_id_fk": { + "name": "card_listId_list_id_fk", + "tableFrom": "card", + "tableTo": "list", + "columnsFrom": [ + "listId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_importId_import_id_fk": { + "name": "card_importId_import_id_fk", + "tableFrom": "card", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_publicId_unique": { + "name": "card_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public._card_labels": { + "name": "_card_labels", + "schema": "", + "columns": { + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "labelId": { + "name": "labelId", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "_card_labels_cardId_card_id_fk": { + "name": "_card_labels_cardId_card_id_fk", + "tableFrom": "_card_labels", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "_card_labels_labelId_label_id_fk": { + "name": "_card_labels_labelId_label_id_fk", + "tableFrom": "_card_labels", + "tableTo": "label", + "columnsFrom": [ + "labelId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "_card_labels_cardId_labelId_pk": { + "name": "_card_labels_cardId_labelId_pk", + "columns": [ + "cardId", + "labelId" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_comments": { + "name": "card_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "parentCommentPublicId": { + "name": "parentCommentPublicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_comments_cardId_card_id_fk": { + "name": "card_comments_cardId_card_id_fk", + "tableFrom": "card_comments", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_comments_createdBy_user_id_fk": { + "name": "card_comments_createdBy_user_id_fk", + "tableFrom": "card_comments", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_comments_deletedBy_user_id_fk": { + "name": "card_comments_deletedBy_user_id_fk", + "tableFrom": "card_comments", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_comments_publicId_unique": { + "name": "card_comments_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_checklist_item": { + "name": "card_checklist_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "completed": { + "name": "completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "checklistId": { + "name": "checklistId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_checklist_item_checklistId_card_checklist_id_fk": { + "name": "card_checklist_item_checklistId_card_checklist_id_fk", + "tableFrom": "card_checklist_item", + "tableTo": "card_checklist", + "columnsFrom": [ + "checklistId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_checklist_item_createdBy_user_id_fk": { + "name": "card_checklist_item_createdBy_user_id_fk", + "tableFrom": "card_checklist_item", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_checklist_item_deletedBy_user_id_fk": { + "name": "card_checklist_item_deletedBy_user_id_fk", + "tableFrom": "card_checklist_item", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_checklist_item_publicId_unique": { + "name": "card_checklist_item_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_checklist": { + "name": "card_checklist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_checklist_cardId_card_id_fk": { + "name": "card_checklist_cardId_card_id_fk", + "tableFrom": "card_checklist", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_checklist_createdBy_user_id_fk": { + "name": "card_checklist_createdBy_user_id_fk", + "tableFrom": "card_checklist", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "card_checklist_deletedBy_user_id_fk": { + "name": "card_checklist_deletedBy_user_id_fk", + "tableFrom": "card_checklist", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_checklist_publicId_unique": { + "name": "card_checklist_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reviewed": { + "name": "reviewed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_createdBy_user_id_fk": { + "name": "feedback_createdBy_user_id_fk", + "tableFrom": "feedback", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.import": { + "name": "import", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "import_createdBy_user_id_fk": { + "name": "import_createdBy_user_id_fk", + "tableFrom": "import", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "import_publicId_unique": { + "name": "import_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.label": { + "name": "label", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "colourCode": { + "name": "colourCode", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "label_createdBy_user_id_fk": { + "name": "label_createdBy_user_id_fk", + "tableFrom": "label", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "label_boardId_board_id_fk": { + "name": "label_boardId_board_id_fk", + "tableFrom": "label", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "label_importId_import_id_fk": { + "name": "label_importId_import_id_fk", + "tableFrom": "label", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "label_deletedBy_user_id_fk": { + "name": "label_deletedBy_user_id_fk", + "tableFrom": "label", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_publicId_unique": { + "name": "label_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.list": { + "name": "list", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "list_createdBy_user_id_fk": { + "name": "list_createdBy_user_id_fk", + "tableFrom": "list", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "list_deletedBy_user_id_fk": { + "name": "list_deletedBy_user_id_fk", + "tableFrom": "list", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "list_boardId_board_id_fk": { + "name": "list_boardId_board_id_fk", + "tableFrom": "list", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "list_importId_import_id_fk": { + "name": "list_importId_import_id_fk", + "tableFrom": "list", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "list_publicId_unique": { + "name": "list_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.integration": { + "name": "integration", + "schema": "", + "columns": { + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refreshToken": { + "name": "refreshToken", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "integration_userId_user_id_fk": { + "name": "integration_userId_user_id_fk", + "tableFrom": "integration", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "integration_pkey": { + "name": "integration_pkey", + "columns": [ + "userId", + "provider" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_slug_checks": { + "name": "workspace_slug_checks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "available": { + "name": "available", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "reserved": { + "name": "reserved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_slug_checks_workspaceId_workspace_id_fk": { + "name": "workspace_slug_checks_workspaceId_workspace_id_fk", + "tableFrom": "workspace_slug_checks", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_slug_checks_createdBy_user_id_fk": { + "name": "workspace_slug_checks_createdBy_user_id_fk", + "tableFrom": "workspace_slug_checks", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_slugs": { + "name": "workspace_slugs", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "slug_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_slugs_slug_unique": { + "name": "workspace_slugs_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_members": { + "name": "workspace_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "roleId": { + "name": "roleId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_members_userId_user_id_fk": { + "name": "workspace_members_userId_user_id_fk", + "tableFrom": "workspace_members", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_members_workspaceId_workspace_id_fk": { + "name": "workspace_members_workspaceId_workspace_id_fk", + "tableFrom": "workspace_members", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_members_deletedBy_user_id_fk": { + "name": "workspace_members_deletedBy_user_id_fk", + "tableFrom": "workspace_members", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_members_roleId_workspace_roles_id_fk": { + "name": "workspace_members_roleId_workspace_roles_id_fk", + "tableFrom": "workspace_members", + "tableTo": "workspace_roles", + "columnsFrom": [ + "roleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_members_publicId_unique": { + "name": "workspace_members_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "workspace_plan", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "showEmailsToMembers": { + "name": "showEmailsToMembers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weekStartDay": { + "name": "weekStartDay", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_createdBy_user_id_fk": { + "name": "workspace_createdBy_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_deletedBy_user_id_fk": { + "name": "workspace_deletedBy_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_publicId_unique": { + "name": "workspace_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + }, + "workspace_slug_unique": { + "name": "workspace_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "referenceId": { + "name": "referenceId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "stripeSubscriptionId": { + "name": "stripeSubscriptionId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "periodStart": { + "name": "periodStart", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "periodEnd": { + "name": "periodEnd", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelAtPeriodEnd": { + "name": "cancelAtPeriodEnd", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unlimitedSeats": { + "name": "unlimitedSeats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trialStart": { + "name": "trialStart", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trialEnd": { + "name": "trialEnd", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "subscription_referenceId_workspace_publicId_fk": { + "name": "subscription_referenceId_workspace_publicId_fk", + "tableFrom": "subscription", + "tableTo": "workspace", + "columnsFrom": [ + "referenceId" + ], + "columnsTo": [ + "publicId" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_invite_links": { + "name": "workspace_invite_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invite_link_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updatedBy": { + "name": "updatedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invite_links_workspaceId_workspace_id_fk": { + "name": "workspace_invite_links_workspaceId_workspace_id_fk", + "tableFrom": "workspace_invite_links", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invite_links_createdBy_user_id_fk": { + "name": "workspace_invite_links_createdBy_user_id_fk", + "tableFrom": "workspace_invite_links", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_invite_links_updatedBy_user_id_fk": { + "name": "workspace_invite_links_updatedBy_user_id_fk", + "tableFrom": "workspace_invite_links", + "tableTo": "user", + "columnsFrom": [ + "updatedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invite_links_publicId_unique": { + "name": "workspace_invite_links_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + }, + "workspace_invite_links_code_unique": { + "name": "workspace_invite_links_code_unique", + "nullsNotDistinct": false, + "columns": [ + "code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_member_permissions": { + "name": "workspace_member_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "granted": { + "name": "granted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "unique_member_permission": { + "name": "unique_member_permission", + "columns": [ + { + "expression": "workspaceMemberId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_member_idx": { + "name": "permission_member_idx", + "columns": [ + { + "expression": "workspaceMemberId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_role_permissions": { + "name": "workspace_role_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "workspaceRoleId": { + "name": "workspaceRoleId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "granted": { + "name": "granted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "unique_role_permission": { + "name": "unique_role_permission", + "columns": [ + { + "expression": "workspaceRoleId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "role_permissions_role_idx": { + "name": "role_permissions_role_idx", + "columns": [ + { + "expression": "workspaceRoleId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_role_permissions_workspaceRoleId_workspace_roles_id_fk": { + "name": "workspace_role_permissions_workspaceRoleId_workspace_roles_id_fk", + "tableFrom": "workspace_role_permissions", + "tableTo": "workspace_roles", + "columnsFrom": [ + "workspaceRoleId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_roles": { + "name": "workspace_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "hierarchyLevel": { + "name": "hierarchyLevel", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "isSystem": { + "name": "isSystem", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "unique_role_per_workspace": { + "name": "unique_role_per_workspace", + "columns": [ + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_roles_workspace_idx": { + "name": "workspace_roles_workspace_idx", + "columns": [ + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_roles_workspaceId_workspace_id_fk": { + "name": "workspace_roles_workspaceId_workspace_id_fk", + "tableFrom": "workspace_roles", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_roles_publicId_unique": { + "name": "workspace_roles_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "commentId": { + "name": "commentId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "readAt": { + "name": "readAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "notification_user_deleted_idx": { + "name": "notification_user_deleted_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deletedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_user_read_deleted_idx": { + "name": "notification_user_read_deleted_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "readAt", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deletedAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_user_type_card_idx": { + "name": "notification_user_type_card_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cardId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_user_type_workspace_idx": { + "name": "notification_user_type_workspace_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_user_created_idx": { + "name": "notification_user_created_idx", + "columns": [ + { + "expression": "userId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_userId_user_id_fk": { + "name": "notification_userId_user_id_fk", + "tableFrom": "notification", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_cardId_card_id_fk": { + "name": "notification_cardId_card_id_fk", + "tableFrom": "notification", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_commentId_card_comments_id_fk": { + "name": "notification_commentId_card_comments_id_fk", + "tableFrom": "notification", + "tableTo": "card_comments", + "columnsFrom": [ + "commentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_workspaceId_workspace_id_fk": { + "name": "notification_workspaceId_workspace_id_fk", + "tableFrom": "notification", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "notification_publicId_unique": { + "name": "notification_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_webhooks": { + "name": "workspace_webhooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "events": { + "name": "events", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_webhooks_workspace_idx": { + "name": "workspace_webhooks_workspace_idx", + "columns": [ + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_webhooks_workspaceId_workspace_id_fk": { + "name": "workspace_webhooks_workspaceId_workspace_id_fk", + "tableFrom": "workspace_webhooks", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_webhooks_createdBy_user_id_fk": { + "name": "workspace_webhooks_createdBy_user_id_fk", + "tableFrom": "workspace_webhooks", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_webhooks_publicId_unique": { + "name": "workspace_webhooks_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": { + "public.board_type": { + "name": "board_type", + "schema": "public", + "values": [ + "regular", + "template" + ] + }, + "public.board_visibility": { + "name": "board_visibility", + "schema": "public", + "values": [ + "private", + "public" + ] + }, + "public.card_activity_type": { + "name": "card_activity_type", + "schema": "public", + "values": [ + "card.created", + "card.updated.title", + "card.updated.description", + "card.updated.index", + "card.updated.list", + "card.updated.label.added", + "card.updated.label.removed", + "card.updated.member.added", + "card.updated.member.removed", + "card.updated.comment.added", + "card.updated.comment.updated", + "card.updated.comment.deleted", + "card.updated.checklist.added", + "card.updated.checklist.renamed", + "card.updated.checklist.deleted", + "card.updated.checklist.item.added", + "card.updated.checklist.item.updated", + "card.updated.checklist.item.completed", + "card.updated.checklist.item.uncompleted", + "card.updated.checklist.item.deleted", + "card.updated.attachment.added", + "card.updated.attachment.removed", + "card.updated.dueDate.added", + "card.updated.dueDate.updated", + "card.updated.dueDate.removed", + "card.archived" + ] + }, + "public.source": { + "name": "source", + "schema": "public", + "values": [ + "trello", + "github" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "started", + "success", + "failed" + ] + }, + "public.role": { + "name": "role", + "schema": "public", + "values": [ + "admin", + "member", + "guest" + ] + }, + "public.member_status": { + "name": "member_status", + "schema": "public", + "values": [ + "invited", + "active", + "removed", + "paused" + ] + }, + "public.slug_type": { + "name": "slug_type", + "schema": "public", + "values": [ + "reserved", + "premium" + ] + }, + "public.workspace_plan": { + "name": "workspace_plan", + "schema": "public", + "values": [ + "free", + "pro", + "enterprise" + ] + }, + "public.invite_link_status": { + "name": "invite_link_status", + "schema": "public", + "values": [ + "active", + "inactive" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "mention", + "workspace.member.added", + "workspace.member.removed", + "workspace.role.changed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 7f6f71f7b..b358cfd4d 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -218,6 +218,13 @@ "when": 1773212242728, "tag": "20260311065722_AddWeekStartDay", "breakpoints": true + }, + { + "idx": 31, + "version": "7", + "when": 1773261982218, + "tag": "20260311204622_AddParentCommentToCardComments", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/repository/card.repo.ts b/packages/db/src/repository/card.repo.ts index 431d00ec4..53eb847ab 100644 --- a/packages/db/src/repository/card.repo.ts +++ b/packages/db/src/repository/card.repo.ts @@ -93,7 +93,11 @@ export const create = async ( index: index, dueDate: cardInput.dueDate ?? null, }) - .returning({ id: cards.id, listId: cards.listId, publicId: cards.publicId }); + .returning({ + id: cards.id, + listId: cards.listId, + publicId: cards.publicId, + }); if (!result[0]) throw new Error("Unable to create card"); @@ -609,6 +613,7 @@ export const getWithListAndMembersByPublicId = async ( comment: { columns: { publicId: true, + parentCommentPublicId: true, comment: true, createdBy: true, updatedAt: true, diff --git a/packages/db/src/repository/cardActivity.repo.ts b/packages/db/src/repository/cardActivity.repo.ts index 2ac40a325..d5ef7c998 100644 --- a/packages/db/src/repository/cardActivity.repo.ts +++ b/packages/db/src/repository/cardActivity.repo.ts @@ -112,14 +112,31 @@ export const getPaginatedActivities = async ( const limit = options?.limit ?? 20; const cursor = options?.cursor; - const validComments = await db - .select({ id: comments.id }) + const topLevelComments = await db + .select({ id: comments.id, publicId: comments.publicId }) .from(comments) - .where(and(eq(comments.cardId, cardId), isNull(comments.deletedAt))); + .where( + and( + eq(comments.cardId, cardId), + isNull(comments.deletedAt), + isNull(comments.parentCommentPublicId), + ), + ); + + const topLevelCommentIds = topLevelComments.map((comment) => comment.id); - const validCommentIds = validComments.map((comment) => comment.id); + const baseWhere = and( + eq(cardActivities.cardId, cardId), + cursor ? gt(cardActivities.createdAt, cursor) : undefined, + topLevelCommentIds.length > 0 + ? or( + isNull(cardActivities.commentId), + inArray(cardActivities.commentId, topLevelCommentIds), + ) + : isNull(cardActivities.commentId), + ); - const activities = await db.query.cardActivities.findMany({ + const baseActivities = await db.query.cardActivities.findMany({ columns: { publicId: true, type: true, @@ -133,14 +150,7 @@ export const getPaginatedActivities = async ( fromDueDate: true, toDueDate: true, }, - where: and( - eq(cardActivities.cardId, cardId), - cursor ? gt(cardActivities.createdAt, cursor) : undefined, - or( - isNull(cardActivities.commentId), - inArray(cardActivities.commentId, validCommentIds), - ), - ), + where: baseWhere, with: { fromList: { columns: { @@ -188,6 +198,7 @@ export const getPaginatedActivities = async ( comment: { columns: { publicId: true, + parentCommentPublicId: true, comment: true, createdBy: true, updatedAt: true, @@ -206,12 +217,150 @@ export const getPaginatedActivities = async ( limit: limit + 1, // fetch one extra to check if there are more }); - const hasMore = activities.length > limit; - const items = activities.slice(0, limit); - const nextCursor = hasMore ? items[items.length - 1]?.createdAt : undefined; + const hasMore = baseActivities.length > limit; + const baseItems = baseActivities.slice(0, limit); + const nextCursor = hasMore + ? baseItems[baseItems.length - 1]?.createdAt + : undefined; + + const topLevelCommentPublicIdsInPage = baseItems + .filter((activity) => activity.type === "card.updated.comment.added") + .map((activity) => activity.comment?.publicId) + .filter((publicId): publicId is string => !!publicId); + + const replyCommentIds: number[] = []; + if (topLevelCommentPublicIdsInPage.length > 0) { + let frontier = topLevelCommentPublicIdsInPage; + const seen = new Set(frontier); + let depth = 0; + + while (frontier.length > 0 && depth < 10) { + const children = await db + .select({ id: comments.id, publicId: comments.publicId }) + .from(comments) + .where( + and( + eq(comments.cardId, cardId), + isNull(comments.deletedAt), + inArray(comments.parentCommentPublicId, frontier), + ), + ); + + const nextFrontier: string[] = []; + for (const child of children) { + replyCommentIds.push(child.id); + if (!seen.has(child.publicId)) { + seen.add(child.publicId); + nextFrontier.push(child.publicId); + } + } + + frontier = nextFrontier; + depth += 1; + } + } + + const replyActivities = + replyCommentIds.length > 0 + ? await db.query.cardActivities.findMany({ + columns: { + publicId: true, + type: true, + createdAt: true, + fromIndex: true, + toIndex: true, + fromTitle: true, + toTitle: true, + fromDescription: true, + toDescription: true, + fromDueDate: true, + toDueDate: true, + }, + where: and( + eq(cardActivities.cardId, cardId), + eq(cardActivities.type, "card.updated.comment.added"), + inArray(cardActivities.commentId, replyCommentIds), + ), + with: { + fromList: { + columns: { + publicId: true, + name: true, + index: true, + }, + }, + toList: { + columns: { + publicId: true, + name: true, + index: true, + }, + }, + label: { + columns: { + publicId: true, + name: true, + }, + }, + member: { + columns: { + publicId: true, + }, + with: { + user: { + columns: { + id: true, + name: true, + email: true, + image: true, + }, + }, + }, + }, + user: { + columns: { + id: true, + name: true, + email: true, + image: true, + }, + }, + comment: { + columns: { + publicId: true, + parentCommentPublicId: true, + comment: true, + createdBy: true, + updatedAt: true, + deletedAt: true, + }, + }, + attachment: { + columns: { + publicId: true, + filename: true, + originalFilename: true, + }, + }, + }, + orderBy: asc(cardActivities.createdAt), + }) + : []; + + const activitiesByPublicId = new Map(); + for (const activity of baseItems) { + activitiesByPublicId.set(activity.publicId, activity); + } + for (const activity of replyActivities) { + activitiesByPublicId.set(activity.publicId, activity); + } + + const mergedItems = Array.from(activitiesByPublicId.values()).sort((a, b) => { + return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); + }); return { - activities: items, + activities: mergedItems, hasMore, nextCursor, }; diff --git a/packages/db/src/repository/cardComment.repo.ts b/packages/db/src/repository/cardComment.repo.ts index 31d4e5c9b..47c83b406 100644 --- a/packages/db/src/repository/cardComment.repo.ts +++ b/packages/db/src/repository/cardComment.repo.ts @@ -19,12 +19,14 @@ export const create = async ( cardId: number; comment: string; createdBy: string; + parentCommentPublicId?: string | null; }, ) => { const [result] = await db .insert(comments) .values({ publicId: generateUID(), + parentCommentPublicId: commentInput.parentCommentPublicId ?? null, comment: commentInput.comment, createdBy: commentInput.createdBy, cardId: commentInput.cardId, @@ -32,6 +34,7 @@ export const create = async ( .returning({ id: comments.id, publicId: comments.publicId, + parentCommentPublicId: comments.parentCommentPublicId, comment: comments.comment, }); @@ -43,8 +46,11 @@ export const getByPublicId = (db: dbClient, publicId: string) => { columns: { id: true, publicId: true, + parentCommentPublicId: true, comment: true, createdBy: true, + cardId: true, + deletedAt: true, }, where: eq(comments.publicId, publicId), }); diff --git a/packages/db/src/schema/cards.ts b/packages/db/src/schema/cards.ts index fb5719728..d0732cebe 100644 --- a/packages/db/src/schema/cards.ts +++ b/packages/db/src/schema/cards.ts @@ -259,6 +259,7 @@ export const cardToWorkspaceMembersRelations = relations( export const comments = pgTable("card_comments", { id: bigserial("id", { mode: "number" }).primaryKey(), publicId: varchar("publicId", { length: 12 }).notNull().unique(), + parentCommentPublicId: varchar("parentCommentPublicId", { length: 12 }), comment: text("comment").notNull(), cardId: bigint("cardId", { mode: "number" }) .notNull() @@ -280,6 +281,11 @@ export const commentsRelations = relations(comments, ({ one }) => ({ references: [cards.id], relationName: "commentsCard", }), + parentComment: one(comments, { + fields: [comments.parentCommentPublicId], + references: [comments.publicId], + relationName: "commentsParentComment", + }), createdBy: one(users, { fields: [comments.createdBy], references: [users.id],