feat: allow users to delete entities they have created
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
|||||||
HiOutlineTrash,
|
HiOutlineTrash,
|
||||||
} from "react-icons/hi2";
|
} from "react-icons/hi2";
|
||||||
|
|
||||||
|
import { authClient } from "@kan/auth/client";
|
||||||
|
|
||||||
import Dropdown from "~/components/Dropdown";
|
import Dropdown from "~/components/Dropdown";
|
||||||
import { usePermissions } from "~/hooks/usePermissions";
|
import { usePermissions } from "~/hooks/usePermissions";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
@@ -24,6 +26,7 @@ interface ListProps {
|
|||||||
interface List {
|
interface List {
|
||||||
publicId: string;
|
publicId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
createdBy?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
@@ -41,6 +44,9 @@ export default function List({
|
|||||||
}: ListProps) {
|
}: ListProps) {
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
const { canCreateCard, canEditList, canDeleteList } = usePermissions();
|
const { canCreateCard, canEditList, canDeleteList } = usePermissions();
|
||||||
|
const { data: session } = authClient.useSession();
|
||||||
|
const isCreator = list.createdBy && session?.user.id === list.createdBy;
|
||||||
|
const canEdit = canEditList || isCreator;
|
||||||
|
|
||||||
const openNewCardForm = (publicListId: PublicListId) => {
|
const openNewCardForm = (publicListId: PublicListId) => {
|
||||||
if (!canCreateCard) return;
|
if (!canCreateCard) return;
|
||||||
@@ -62,7 +68,7 @@ export default function List({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = (values: FormValues) => {
|
const onSubmit = (values: FormValues) => {
|
||||||
if (!canEditList) return;
|
if (!canEdit) return;
|
||||||
updateList.mutate({
|
updateList.mutate({
|
||||||
listPublicId: values.listPublicId,
|
listPublicId: values.listPublicId,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
@@ -94,7 +100,7 @@ export default function List({
|
|||||||
type="text"
|
type="text"
|
||||||
{...register("name")}
|
{...register("name")}
|
||||||
onBlur={handleSubmit(onSubmit)}
|
onBlur={handleSubmit(onSubmit)}
|
||||||
readOnly={!canEditList}
|
readOnly={!canEdit}
|
||||||
className="w-full border-0 bg-transparent px-4 pt-1 text-sm font-medium text-neutral-900 focus:ring-0 focus-visible:outline-none dark:text-dark-1000"
|
className="w-full border-0 bg-transparent px-4 pt-1 text-sm font-medium text-neutral-900 focus:ring-0 focus-visible:outline-none dark:text-dark-1000"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
@@ -124,7 +130,7 @@ export default function List({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(canDeleteList
|
...(canDeleteList || isCreator
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t`Delete list`,
|
label: t`Delete list`,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ const Comment = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...((isAuthor || isAdmin) && canDeleteComment
|
...((isAuthor || canDeleteComment)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t`Delete comment`,
|
label: t`Delete comment`,
|
||||||
|
|||||||
@@ -5,13 +5,21 @@ import {
|
|||||||
HiOutlineTrash,
|
HiOutlineTrash,
|
||||||
} from "react-icons/hi2";
|
} from "react-icons/hi2";
|
||||||
|
|
||||||
|
import { authClient } from "@kan/auth/client";
|
||||||
|
|
||||||
import Dropdown from "~/components/Dropdown";
|
import Dropdown from "~/components/Dropdown";
|
||||||
import { usePermissions } from "~/hooks/usePermissions";
|
import { usePermissions } from "~/hooks/usePermissions";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
|
|
||||||
export default function CardDropdown() {
|
export default function CardDropdown({
|
||||||
|
cardCreatedBy,
|
||||||
|
}: {
|
||||||
|
cardCreatedBy?: string | null;
|
||||||
|
}) {
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
const { canEditCard, canDeleteCard } = usePermissions();
|
const { canEditCard, canDeleteCard } = usePermissions();
|
||||||
|
const { data: session } = authClient.useSession();
|
||||||
|
const isCreator = cardCreatedBy && session?.user.id === cardCreatedBy;
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
...(canEditCard
|
...(canEditCard
|
||||||
@@ -25,7 +33,7 @@ export default function CardDropdown() {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(canDeleteCard
|
...(canDeleteCard || isCreator
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: t`Delete card`,
|
label: t`Delete card`,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import Modal from "~/components/modal";
|
|||||||
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
|
import { EditYouTubeModal } from "~/components/YouTubeEmbed/EditYouTubeModal";
|
||||||
|
import { authClient } from "@kan/auth/client";
|
||||||
|
|
||||||
import { usePermissions } from "~/hooks/usePermissions";
|
import { usePermissions } from "~/hooks/usePermissions";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { usePopup } from "~/providers/popup";
|
import { usePopup } from "~/providers/popup";
|
||||||
@@ -46,6 +48,7 @@ interface FormValues {
|
|||||||
export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
|
export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { canEditCard } = usePermissions();
|
const { canEditCard } = usePermissions();
|
||||||
|
const { data: session } = authClient.useSession();
|
||||||
const cardId = Array.isArray(router.query.cardId)
|
const cardId = Array.isArray(router.query.cardId)
|
||||||
? router.query.cardId[0]
|
? router.query.cardId[0]
|
||||||
: router.query.cardId;
|
: router.query.cardId;
|
||||||
@@ -54,6 +57,9 @@ export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
cardPublicId: cardId ?? "",
|
cardPublicId: cardId ?? "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isCreator = card?.createdBy && session?.user.id === card.createdBy;
|
||||||
|
const canEdit = canEditCard || isCreator;
|
||||||
|
|
||||||
const board = card?.list.board;
|
const board = card?.list.board;
|
||||||
const labels = board?.labels;
|
const labels = board?.labels;
|
||||||
const workspaceMembers = board?.workspace.members;
|
const workspaceMembers = board?.workspace.members;
|
||||||
@@ -112,7 +118,7 @@ export function CardRightPanel({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-[360px] border-l-[1px] border-light-300 bg-light-50 p-8 text-light-900 dark:border-dark-300 dark:bg-dark-50 dark:text-dark-900">
|
<div className="h-full w-[360px] border-l-[1px] border-light-300 bg-light-50 p-8 text-light-900 dark:border-dark-300 dark:bg-dark-50 dark:text-dark-900">
|
||||||
{canEditCard && (
|
{canEdit && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4 flex w-full flex-row pt-[18px]">
|
<div className="mb-4 flex w-full flex-row pt-[18px]">
|
||||||
<p className="my-2 mb-2 w-[100px] text-sm font-medium">{t`List`}</p>
|
<p className="my-2 mb-2 w-[100px] text-sm font-medium">{t`List`}</p>
|
||||||
@@ -169,6 +175,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
const { showPopup } = usePopup();
|
const { showPopup } = usePopup();
|
||||||
const { workspace } = useWorkspace();
|
const { workspace } = useWorkspace();
|
||||||
const { canEditCard } = usePermissions();
|
const { canEditCard } = usePermissions();
|
||||||
|
const { data: session } = authClient.useSession();
|
||||||
const [activeChecklistForm, setActiveChecklistForm] = useState<string | null>(
|
const [activeChecklistForm, setActiveChecklistForm] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -181,6 +188,9 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
cardPublicId: cardId ?? "",
|
cardPublicId: cardId ?? "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isCreator = card?.createdBy && session?.user.id === card.createdBy;
|
||||||
|
const canEdit = canEditCard || isCreator;
|
||||||
|
|
||||||
const refetchCard = async () => {
|
const refetchCard = async () => {
|
||||||
if (cardId) await utils.card.byId.refetch({ cardPublicId: cardId });
|
if (cardId) await utils.card.byId.refetch({ cardPublicId: cardId });
|
||||||
};
|
};
|
||||||
@@ -305,7 +315,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Dropdown />
|
<Dropdown cardCreatedBy={card?.createdBy} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -333,10 +343,10 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
<textarea
|
<textarea
|
||||||
id="title"
|
id="title"
|
||||||
{...register("title")}
|
{...register("title")}
|
||||||
onBlur={canEditCard ? handleSubmit(onSubmit) : undefined}
|
onBlur={canEdit ? handleSubmit(onSubmit) : undefined}
|
||||||
rows={1}
|
rows={1}
|
||||||
disabled={!canEditCard}
|
disabled={!canEdit}
|
||||||
className={`block w-full resize-none overflow-hidden border-0 bg-transparent p-0 py-0 font-bold leading-relaxed text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem] ${!canEditCard ? "cursor-default" : ""}`}
|
className={`block w-full resize-none overflow-hidden border-0 bg-transparent p-0 py-0 font-bold leading-relaxed text-neutral-900 focus:ring-0 dark:text-dark-1000 sm:text-[1.2rem] ${!canEdit ? "cursor-default" : ""}`}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const target = e.target as HTMLTextAreaElement;
|
const target = e.target as HTMLTextAreaElement;
|
||||||
target.style.height = "auto";
|
target.style.height = "auto";
|
||||||
@@ -362,10 +372,10 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Editor
|
<Editor
|
||||||
content={card.description}
|
content={card.description}
|
||||||
onChange={canEditCard ? (e) => setValue("description", e) : undefined}
|
onChange={canEdit ? (e) => setValue("description", e) : undefined}
|
||||||
onBlur={canEditCard ? () => handleSubmit(onSubmit)() : undefined}
|
onBlur={canEdit ? () => handleSubmit(onSubmit)() : undefined}
|
||||||
workspaceMembers={board?.workspace.members ?? []}
|
workspaceMembers={board?.workspace.members ?? []}
|
||||||
readOnly={!canEditCard}
|
readOnly={!canEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -375,7 +385,7 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
cardPublicId={cardId}
|
cardPublicId={cardId}
|
||||||
activeChecklistForm={activeChecklistForm}
|
activeChecklistForm={activeChecklistForm}
|
||||||
setActiveChecklistForm={setActiveChecklistForm}
|
setActiveChecklistForm={setActiveChecklistForm}
|
||||||
viewOnly={!canEditCard}
|
viewOnly={!canEdit}
|
||||||
/>
|
/>
|
||||||
{!isTemplate && (
|
{!isTemplate && (
|
||||||
<>
|
<>
|
||||||
@@ -384,11 +394,11 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) {
|
|||||||
<AttachmentThumbnails
|
<AttachmentThumbnails
|
||||||
attachments={card.attachments}
|
attachments={card.attachments}
|
||||||
cardPublicId={cardId ?? ""}
|
cardPublicId={cardId ?? ""}
|
||||||
isReadOnly={!canEditCard}
|
isReadOnly={!canEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{canEditCard && (
|
{canEdit && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<AttachmentUpload cardPublicId={cardId} />
|
<AttachmentUpload cardPublicId={cardId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from "@kan/shared/utils";
|
} from "@kan/shared/utils";
|
||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
import { assertPermission } from "../utils/permissions";
|
import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions";
|
||||||
|
|
||||||
export const boardRouter = createTRPCRouter({
|
export const boardRouter = createTRPCRouter({
|
||||||
all: protectedProcedure
|
all: protectedProcedure
|
||||||
@@ -422,7 +422,13 @@ export const boardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, board.workspaceId, "board:edit");
|
await assertCanEdit(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
board.workspaceId,
|
||||||
|
"board:edit",
|
||||||
|
board.createdBy ?? null,
|
||||||
|
);
|
||||||
|
|
||||||
if (input.slug) {
|
if (input.slug) {
|
||||||
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
|
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
|
||||||
@@ -491,7 +497,13 @@ export const boardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, board.workspaceId, "board:delete");
|
await assertCanDelete(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
board.workspaceId,
|
||||||
|
"board:delete",
|
||||||
|
board.createdBy ?? null,
|
||||||
|
);
|
||||||
|
|
||||||
const listIds = board.lists.map((list) => list.id);
|
const listIds = board.lists.map((list) => list.id);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as workspaceRepo from "@kan/db/repository/workspace.repo";
|
|||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||||
import { mergeActivities } from "../utils/activities";
|
import { mergeActivities } from "../utils/activities";
|
||||||
import { assertPermission } from "../utils/permissions";
|
import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions";
|
||||||
import { generateDownloadUrl } from "../utils/s3";
|
import { generateDownloadUrl } from "../utils/s3";
|
||||||
|
|
||||||
export const cardRouter = createTRPCRouter({
|
export const cardRouter = createTRPCRouter({
|
||||||
@@ -256,8 +256,6 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, card.workspaceId, "comment:edit");
|
|
||||||
|
|
||||||
const existingComment = await cardCommentRepo.getByPublicId(
|
const existingComment = await cardCommentRepo.getByPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
input.commentPublicId,
|
input.commentPublicId,
|
||||||
@@ -269,11 +267,13 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingComment.createdBy !== userId)
|
await assertCanEdit(
|
||||||
throw new TRPCError({
|
ctx.db,
|
||||||
message: `You do not have permission to update this comment`,
|
userId,
|
||||||
code: "FORBIDDEN",
|
card.workspaceId,
|
||||||
});
|
"comment:edit",
|
||||||
|
existingComment.createdBy,
|
||||||
|
);
|
||||||
|
|
||||||
const updatedComment = await cardCommentRepo.update(ctx.db, {
|
const updatedComment = await cardCommentRepo.update(ctx.db, {
|
||||||
id: existingComment.id,
|
id: existingComment.id,
|
||||||
@@ -334,8 +334,6 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, card.workspaceId, "comment:delete");
|
|
||||||
|
|
||||||
const existingComment = await cardCommentRepo.getByPublicId(
|
const existingComment = await cardCommentRepo.getByPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
input.commentPublicId,
|
input.commentPublicId,
|
||||||
@@ -347,6 +345,14 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await assertCanDelete(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
card.workspaceId,
|
||||||
|
"comment:delete",
|
||||||
|
existingComment.createdBy,
|
||||||
|
);
|
||||||
|
|
||||||
const deletedComment = await cardCommentRepo.softDelete(ctx.db, {
|
const deletedComment = await cardCommentRepo.softDelete(ctx.db, {
|
||||||
commentId: existingComment.id,
|
commentId: existingComment.id,
|
||||||
deletedAt: new Date(),
|
deletedAt: new Date(),
|
||||||
@@ -782,7 +788,13 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, card.workspaceId, "card:edit");
|
await assertCanEdit(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
card.workspaceId,
|
||||||
|
"card:edit",
|
||||||
|
card.createdBy,
|
||||||
|
);
|
||||||
|
|
||||||
const existingCard = await cardRepo.getByPublicId(
|
const existingCard = await cardRepo.getByPublicId(
|
||||||
ctx.db,
|
ctx.db,
|
||||||
@@ -952,7 +964,13 @@ export const cardRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, card.workspaceId, "card:delete");
|
await assertCanDelete(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
card.workspaceId,
|
||||||
|
"card:delete",
|
||||||
|
card.createdBy,
|
||||||
|
);
|
||||||
|
|
||||||
const deletedAt = new Date();
|
const deletedAt = new Date();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as activityRepo from "@kan/db/repository/cardActivity.repo";
|
|||||||
import * as listRepo from "@kan/db/repository/list.repo";
|
import * as listRepo from "@kan/db/repository/list.repo";
|
||||||
|
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
import { assertPermission } from "../utils/permissions";
|
import { assertCanDelete, assertCanEdit, assertPermission } from "../utils/permissions";
|
||||||
|
|
||||||
export const listRouter = createTRPCRouter({
|
export const listRouter = createTRPCRouter({
|
||||||
create: protectedProcedure
|
create: protectedProcedure
|
||||||
@@ -101,7 +101,13 @@ export const listRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, list.workspaceId, "list:delete");
|
await assertCanDelete(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
list.workspaceId,
|
||||||
|
"list:delete",
|
||||||
|
list.createdBy,
|
||||||
|
);
|
||||||
|
|
||||||
const deletedAt = new Date();
|
const deletedAt = new Date();
|
||||||
|
|
||||||
@@ -183,7 +189,13 @@ export const listRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await assertPermission(ctx.db, userId, list.workspaceId, "list:edit");
|
await assertCanEdit(
|
||||||
|
ctx.db,
|
||||||
|
userId,
|
||||||
|
list.workspaceId,
|
||||||
|
"list:edit",
|
||||||
|
list.createdBy,
|
||||||
|
);
|
||||||
|
|
||||||
let result: { name: string; publicId: string } | undefined;
|
let result: { name: string; publicId: string } | undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ export async function assertCanManageRole(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const managerRole = managerMember.role as Role;
|
const managerRole = managerMember.role;
|
||||||
|
|
||||||
if (!canManageRole(managerRole, targetRoleName as Role)) {
|
if (!canManageRole(managerRole, targetRoleName as Role)) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -223,8 +223,8 @@ export async function assertCanManageMember(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const managerRole = managerMember.role as Role;
|
const managerRole = managerMember.role;
|
||||||
const targetRole = targetMember.role as Role;
|
const targetRole = targetMember.role;
|
||||||
|
|
||||||
if (!canManageRole(managerRole, targetRole)) {
|
if (!canManageRole(managerRole, targetRole)) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -233,3 +233,63 @@ export async function assertCanManageMember(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert user can delete an entity - either has the delete permission OR is the creator
|
||||||
|
*/
|
||||||
|
export async function assertCanDelete(
|
||||||
|
db: dbClient,
|
||||||
|
userId: string,
|
||||||
|
workspaceId: number,
|
||||||
|
permission: Permission,
|
||||||
|
createdBy: string | null,
|
||||||
|
): Promise<void> {
|
||||||
|
// Check if user has the general delete permission
|
||||||
|
const hasDeletePermission = await hasPermission(db, userId, workspaceId, permission);
|
||||||
|
|
||||||
|
// If user has permission, allow deletion
|
||||||
|
if (hasDeletePermission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user doesn't have permission, check if they are the creator
|
||||||
|
if (createdBy && createdBy === userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither condition met - deny deletion
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `You do not have permission to delete this entity (${permission})`,
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert user can edit an entity - either has the edit permission OR is the creator
|
||||||
|
*/
|
||||||
|
export async function assertCanEdit(
|
||||||
|
db: dbClient,
|
||||||
|
userId: string,
|
||||||
|
workspaceId: number,
|
||||||
|
permission: Permission,
|
||||||
|
createdBy: string | null,
|
||||||
|
): Promise<void> {
|
||||||
|
// Check if user has the general edit permission
|
||||||
|
const hasEditPermission = await hasPermission(db, userId, workspaceId, permission);
|
||||||
|
|
||||||
|
// If user has permission, allow editing
|
||||||
|
if (hasEditPermission) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user doesn't have permission, check if they are the creator
|
||||||
|
if (createdBy && createdBy === userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither condition met - deny editing
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `You do not have permission to edit this entity (${permission})`,
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -518,6 +518,7 @@ export const getWithListIdsByPublicId = (
|
|||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
workspaceId: true,
|
workspaceId: true,
|
||||||
|
createdBy: true,
|
||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
lists: {
|
lists: {
|
||||||
@@ -671,6 +672,7 @@ export const getWorkspaceAndBoardIdByBoardPublicId = async (
|
|||||||
columns: {
|
columns: {
|
||||||
id: true,
|
id: true,
|
||||||
workspaceId: true,
|
workspaceId: true,
|
||||||
|
createdBy: true,
|
||||||
},
|
},
|
||||||
where: eq(boards.publicId, boardPublicId),
|
where: eq(boards.publicId, boardPublicId),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -424,6 +424,7 @@ export const getWithListAndMembersByPublicId = async (
|
|||||||
title: true,
|
title: true,
|
||||||
description: true,
|
description: true,
|
||||||
dueDate: true,
|
dueDate: true,
|
||||||
|
createdBy: true,
|
||||||
},
|
},
|
||||||
with: {
|
with: {
|
||||||
labels: {
|
labels: {
|
||||||
@@ -938,7 +939,7 @@ export const getWorkspaceAndCardIdByCardPublicId = async (
|
|||||||
cardPublicId: string,
|
cardPublicId: string,
|
||||||
) => {
|
) => {
|
||||||
const result = await db.query.cards.findFirst({
|
const result = await db.query.cards.findFirst({
|
||||||
columns: { id: true },
|
columns: { id: true, createdBy: true },
|
||||||
where: and(eq(cards.publicId, cardPublicId), isNull(cards.deletedAt)),
|
where: and(eq(cards.publicId, cardPublicId), isNull(cards.deletedAt)),
|
||||||
with: {
|
with: {
|
||||||
list: {
|
list: {
|
||||||
@@ -958,6 +959,7 @@ export const getWorkspaceAndCardIdByCardPublicId = async (
|
|||||||
return result
|
return result
|
||||||
? {
|
? {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
|
createdBy: result.createdBy,
|
||||||
workspaceId: result.list.board.workspaceId,
|
workspaceId: result.list.board.workspaceId,
|
||||||
workspaceVisibility: result.list.board.visibility,
|
workspaceVisibility: result.list.board.visibility,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,7 +419,7 @@ export const getWorkspaceAndListIdByListPublicId = async (
|
|||||||
listPublicId: string,
|
listPublicId: string,
|
||||||
) => {
|
) => {
|
||||||
const result = await db.query.lists.findFirst({
|
const result = await db.query.lists.findFirst({
|
||||||
columns: { id: true },
|
columns: { id: true, createdBy: true },
|
||||||
where: and(eq(lists.publicId, listPublicId), isNull(lists.deletedAt)),
|
where: and(eq(lists.publicId, listPublicId), isNull(lists.deletedAt)),
|
||||||
with: {
|
with: {
|
||||||
board: {
|
board: {
|
||||||
@@ -431,6 +431,10 @@ export const getWorkspaceAndListIdByListPublicId = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
return result
|
return result
|
||||||
? { id: result.id, workspaceId: result.board.workspaceId }
|
? {
|
||||||
|
id: result.id,
|
||||||
|
createdBy: result.createdBy,
|
||||||
|
workspaceId: result.board.workspaceId,
|
||||||
|
}
|
||||||
: null;
|
: null;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user