feat: delete workspace
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
variant?: "primary" | "secondary";
|
variant?: "primary" | "secondary" | "danger";
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,8 +19,11 @@ const Button = ({
|
|||||||
"bg-light-1000 dark:bg-dark-1000 dark:text-dark-50",
|
"bg-light-1000 dark:bg-dark-1000 dark:text-dark-50",
|
||||||
variant === "secondary" &&
|
variant === "secondary" &&
|
||||||
"border-[1px] border-light-600 bg-light-50 dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000",
|
"border-[1px] border-light-600 bg-light-50 dark:border-dark-600 dark:bg-dark-300 dark:text-dark-1000",
|
||||||
|
variant === "danger" &&
|
||||||
|
"dark:text-red-1000 border-[1px] border-red-600 bg-red-50 dark:border-red-600 dark:bg-red-500",
|
||||||
|
props.disabled && "opacity-50",
|
||||||
)}
|
)}
|
||||||
disabled={isLoading}
|
disabled={isLoading ?? props.disabled}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<span className="relative flex items-center justify-center">
|
<span className="relative flex items-center justify-center">
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { HiXMark } from "react-icons/hi2";
|
|
||||||
import { useModal } from "~/providers/modal";
|
|
||||||
import { useWorkspace } from "~/providers/workspace";
|
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { HiXMark } from "react-icons/hi2";
|
||||||
|
|
||||||
|
import { useModal } from "~/providers/modal";
|
||||||
|
import { usePopup } from "~/providers/popup";
|
||||||
|
import { useWorkspace } from "~/providers/workspace";
|
||||||
|
|
||||||
|
import Button from "~/components/Button";
|
||||||
|
import Input from "~/components/Input";
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -11,20 +16,23 @@ interface FormValues {
|
|||||||
|
|
||||||
export function NewWorkspaceForm() {
|
export function NewWorkspaceForm() {
|
||||||
const { closeModal } = useModal();
|
const { closeModal } = useModal();
|
||||||
|
const { showPopup } = usePopup();
|
||||||
const { switchWorkspace } = useWorkspace();
|
const { switchWorkspace } = useWorkspace();
|
||||||
const { register, handleSubmit } = useForm<FormValues>();
|
const { register, handleSubmit } = useForm<FormValues>();
|
||||||
|
|
||||||
const createWorkspace = api.workspace.create.useMutation({
|
const createWorkspace = api.workspace.create.useMutation({
|
||||||
onSuccess: (values) => {
|
onSuccess: (values) => {
|
||||||
try {
|
if (values?.publicId && values.name) {
|
||||||
if (values?.publicId && values.name) {
|
switchWorkspace({ publicId: values.publicId, name: values.name });
|
||||||
switchWorkspace({ publicId: values.publicId, name: values.name });
|
closeModal();
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onError: () => {
|
||||||
|
showPopup({
|
||||||
|
header: "Unable to create workspace",
|
||||||
|
message: "Please try again later, or contact customer support.",
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -40,40 +48,36 @@ export function NewWorkspaceForm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-5">
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="flex w-full items-center justify-between pb-4">
|
<div className="px-5 pt-5">
|
||||||
<h2 className="text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
<div className="flex w-full items-center justify-between pb-4">
|
||||||
New workspace
|
<h2 className="text-sm font-bold text-neutral-900 dark:text-dark-1000">
|
||||||
</h2>
|
New workspace
|
||||||
<button
|
</h2>
|
||||||
className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
|
|
||||||
onClick={() => closeModal()}
|
|
||||||
>
|
|
||||||
<HiXMark size={18} className="text-light-900 dark:text-dark-900" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<label
|
|
||||||
htmlFor="workspace-name"
|
|
||||||
className="block pb-2 text-sm font-normal leading-6 text-neutral-900 dark:text-dark-1000"
|
|
||||||
>
|
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="workspace-name"
|
|
||||||
{...register("name", { required: true })}
|
|
||||||
className="block w-full rounded-md border-0 bg-dark-300 bg-white/5 py-1.5 text-neutral-900 shadow-sm ring-1 ring-inset ring-light-600 focus:ring-2 focus:ring-inset focus:ring-light-600 dark:text-dark-1000 dark:ring-dark-700 dark:focus:ring-dark-700 sm:text-sm sm:leading-6"
|
|
||||||
/>
|
|
||||||
<div className="mt-5 sm:mt-6">
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
className="rounded p-1 hover:bg-light-200 focus:outline-none dark:hover:bg-dark-300"
|
||||||
className="inline-flex w-full justify-center rounded-md bg-light-1000 px-3 py-2 text-sm font-semibold text-light-50 shadow-sm focus-visible:outline-none dark:bg-dark-1000 dark:text-dark-50"
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
closeModal();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Create workspace
|
<HiXMark size={18} className="text-light-900 dark:text-dark-900" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
<Input
|
||||||
|
id="workspace-name"
|
||||||
|
placeholder="Workspace name"
|
||||||
|
{...register("name")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-12 flex items-center justify-end border-t border-light-600 px-5 pb-5 pt-5 dark:border-dark-600">
|
||||||
|
<div>
|
||||||
|
<Button type="submit" isLoading={createWorkspace.isPending}>
|
||||||
|
Create workspace
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,20 +114,20 @@ export const boardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await boardRepo.destroy(ctx.db, {
|
await boardRepo.softDelete(ctx.db, {
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (listIds.length) {
|
if (listIds.length) {
|
||||||
await listRepo.destroyAllByBoardId(ctx.db, {
|
await listRepo.softDeleteAllByBoardId(ctx.db, {
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await cardRepo.destroyAllByListIds(ctx.db, {
|
await cardRepo.softDeleteAllByListIds(ctx.db, {
|
||||||
listIds,
|
listIds,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingLabel) {
|
if (existingLabel) {
|
||||||
await cardRepo.destroyCardLabelRelationship(ctx.db, cardLabelIds);
|
await cardRepo.hardDeleteCardLabelRelationship(ctx.db, cardLabelIds);
|
||||||
|
|
||||||
return { newLabel: false };
|
return { newLabel: false };
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingMember) {
|
if (existingMember) {
|
||||||
await cardRepo.destroyCardMemberRelationship(ctx.db, cardMemberIds);
|
await cardRepo.hardDeleteCardMemberRelationship(ctx.db, cardMemberIds);
|
||||||
|
|
||||||
return { newMember: false };
|
return { newMember: false };
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ export const cardRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await cardRepo.destroy(ctx.db, {
|
await cardRepo.softDelete(ctx.db, {
|
||||||
cardId: card.id,
|
cardId: card.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ export const labelRouter = createTRPCRouter({
|
|||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
});
|
});
|
||||||
|
|
||||||
await cardRepo.destroyAllCardLabelRelationships(ctx.db, label.id);
|
await cardRepo.hardDeleteAllCardLabelRelationships(ctx.db, label.id);
|
||||||
|
|
||||||
await labelRepo.destroy(ctx.db, label.id);
|
await labelRepo.hardDelete(ctx.db, label.id);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -98,13 +98,13 @@ export const listRouter = createTRPCRouter({
|
|||||||
|
|
||||||
const deletedAt = new Date().toISOString();
|
const deletedAt = new Date().toISOString();
|
||||||
|
|
||||||
await listRepo.destroyById(ctx.db, {
|
await listRepo.softDeleteById(ctx.db, {
|
||||||
listId: list.id,
|
listId: list.id,
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await cardRepo.destroyAllByListIds(ctx.db, {
|
await cardRepo.softDeleteAllByListIds(ctx.db, {
|
||||||
listIds: [list.id],
|
listIds: [list.id],
|
||||||
deletedAt,
|
deletedAt,
|
||||||
deletedBy: userId,
|
deletedBy: userId,
|
||||||
|
|||||||
@@ -49,6 +49,22 @@ export const workspaceRouter = createTRPCRouter({
|
|||||||
createdBy: userId,
|
createdBy: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!result?.publicId)
|
||||||
|
throw new TRPCError({
|
||||||
|
message: `Unable to create workspace`,
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}),
|
}),
|
||||||
|
delete: protectedProcedure
|
||||||
|
.input(z.object({ workspacePublicId: z.string().min(12) }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const { data } = await workspaceRepo.hardDelete(
|
||||||
|
ctx.db,
|
||||||
|
input.workspacePublicId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
66
src/server/db/migrations/0002_clever_robin_chapel.sql
Normal file
66
src/server/db/migrations/0002_clever_robin_chapel.sql
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
DROP TABLE IF EXISTS "account";--> statement-breakpoint
|
||||||
|
DROP TABLE IF EXISTS "session";--> statement-breakpoint
|
||||||
|
DROP TABLE IF EXISTS "verificationToken";--> statement-breakpoint
|
||||||
|
ALTER TABLE "board" DROP CONSTRAINT "board_workspaceId_workspace_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "_card_workspace_members" DROP CONSTRAINT "_card_workspace_members_cardId_card_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "card" DROP CONSTRAINT "card_listId_list_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "_card_labels" DROP CONSTRAINT IF EXISTS "_card_labels_cardId_card_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "_card_labels" DROP CONSTRAINT IF EXISTS "_card_labels_labelId_label_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "label" DROP CONSTRAINT "label_boardId_board_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "list" DROP CONSTRAINT "list_boardId_board_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "workspace_members" DROP CONSTRAINT "workspace_members_workspaceId_workspace_id_fk";
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "board" ADD CONSTRAINT "board_workspaceId_workspace_id_fk" FOREIGN KEY ("workspaceId") REFERENCES "workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_card_workspace_members" ADD CONSTRAINT "_card_workspace_members_workspaceMemberId_workspace_members_id_fk" FOREIGN KEY ("workspaceMemberId") REFERENCES "workspace_members"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "card" ADD CONSTRAINT "card_listId_list_id_fk" FOREIGN KEY ("listId") REFERENCES "list"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_card_labels" ADD CONSTRAINT "_card_labels_cardId_card_id_fk" FOREIGN KEY ("cardId") REFERENCES "card"("id") ON DELETE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "_card_labels" ADD CONSTRAINT "_card_labels_labelId_label_id_fk" FOREIGN KEY ("labelId") REFERENCES "label"("id") ON DELETE CASCADE;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "label" ADD CONSTRAINT "label_boardId_board_id_fk" FOREIGN KEY ("boardId") REFERENCES "board"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "list" ADD CONSTRAINT "list_boardId_board_id_fk" FOREIGN KEY ("boardId") REFERENCES "board"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspaceId_workspace_id_fk" FOREIGN KEY ("workspaceId") REFERENCES "workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
1003
src/server/db/migrations/meta/0002_snapshot.json
Normal file
1003
src/server/db/migrations/meta/0002_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,13 @@
|
|||||||
"when": 1713021051974,
|
"when": 1713021051974,
|
||||||
"tag": "0001_stale_mattie_franklin",
|
"tag": "0001_stale_mattie_franklin",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "5",
|
||||||
|
"when": 1724967733894,
|
||||||
|
"tag": "0002_clever_robin_chapel",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export const update = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroy = async (
|
export const softDelete = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
args: {
|
args: {
|
||||||
boardId: number;
|
boardId: number;
|
||||||
@@ -160,3 +160,12 @@ export const destroy = async (
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hardDelete = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
workspaceId: number,
|
||||||
|
) => {
|
||||||
|
const result = db.from("board").delete().eq("workspaceId", workspaceId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -74,39 +74,6 @@ export const update = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroy = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
args: {
|
|
||||||
cardId: number;
|
|
||||||
deletedAt: string;
|
|
||||||
deletedBy: string;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const result = await db
|
|
||||||
.from("card")
|
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
|
||||||
.eq("id", args.cardId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const destroyAllByListIds = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
args: {
|
|
||||||
listIds: number[];
|
|
||||||
deletedAt: string;
|
|
||||||
deletedBy: string;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const result = await db
|
|
||||||
.from("card")
|
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
|
||||||
.in("listId", args.listIds)
|
|
||||||
.is("deletedAt", null);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCardWithListByPublicId = async (
|
export const getCardWithListByPublicId = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
cardPublicId: string,
|
cardPublicId: string,
|
||||||
@@ -168,28 +135,6 @@ export const bulkCreate = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroyCardLabelRelationship = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
args: { cardId: number; labelId: number },
|
|
||||||
) => {
|
|
||||||
const result = await db
|
|
||||||
.from("_card_labels")
|
|
||||||
.delete()
|
|
||||||
.eq("cardId", args.cardId)
|
|
||||||
.eq("labelId", args.labelId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const destroyAllCardLabelRelationships = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
labelId: number,
|
|
||||||
) => {
|
|
||||||
const result = await db.from("_card_labels").delete().eq("labelId", labelId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createCardLabelRelationship = async (
|
export const createCardLabelRelationship = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
cardLabelRelationshipInput: { cardId: number; labelId: number },
|
cardLabelRelationshipInput: { cardId: number; labelId: number },
|
||||||
@@ -217,19 +162,6 @@ export const getCardMemberRelationship = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroyCardMemberRelationship = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
args: { cardId: number; memberId: number },
|
|
||||||
) => {
|
|
||||||
const result = await db
|
|
||||||
.from("_card_workspace_members")
|
|
||||||
.delete()
|
|
||||||
.eq("cardId", args.cardId)
|
|
||||||
.eq("workspaceMemberId", args.memberId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createCardMemberRelationship = async (
|
export const createCardMemberRelationship = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
cardMemberRelationshipInput: { cardId: number; memberId: number },
|
cardMemberRelationshipInput: { cardId: number; memberId: number },
|
||||||
@@ -353,3 +285,71 @@ export const pushIndex = async (
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const softDelete = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
args: {
|
||||||
|
cardId: number;
|
||||||
|
deletedAt: string;
|
||||||
|
deletedBy: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const result = await db
|
||||||
|
.from("card")
|
||||||
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
|
.eq("id", args.cardId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const softDeleteAllByListIds = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
args: {
|
||||||
|
listIds: number[];
|
||||||
|
deletedAt: string;
|
||||||
|
deletedBy: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const result = await db
|
||||||
|
.from("card")
|
||||||
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
|
.in("listId", args.listIds)
|
||||||
|
.is("deletedAt", null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hardDeleteCardMemberRelationship = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
args: { cardId: number; memberId: number },
|
||||||
|
) => {
|
||||||
|
const result = await db
|
||||||
|
.from("_card_workspace_members")
|
||||||
|
.delete()
|
||||||
|
.eq("cardId", args.cardId)
|
||||||
|
.eq("workspaceMemberId", args.memberId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hardDeleteCardLabelRelationship = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
args: { cardId: number; labelId: number },
|
||||||
|
) => {
|
||||||
|
const result = await db
|
||||||
|
.from("_card_labels")
|
||||||
|
.delete()
|
||||||
|
.eq("cardId", args.cardId)
|
||||||
|
.eq("labelId", args.labelId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const hardDeleteAllCardLabelRelationships = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
labelId: number,
|
||||||
|
) => {
|
||||||
|
const result = await db.from("_card_labels").delete().eq("labelId", labelId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const update = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroy = async (
|
export const hardDelete = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
labelId: number,
|
labelId: number,
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@@ -85,40 +85,6 @@ export const update = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroyAllByBoardId = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
args: {
|
|
||||||
boardId: number;
|
|
||||||
deletedAt: string;
|
|
||||||
deletedBy: string;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const result = await db
|
|
||||||
.from("list")
|
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
|
||||||
.eq("boardId", args.boardId)
|
|
||||||
.is("deletedAt", null);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const destroyById = async (
|
|
||||||
db: SupabaseClient<Database>,
|
|
||||||
args: {
|
|
||||||
listId: number;
|
|
||||||
deletedAt: string;
|
|
||||||
deletedBy: string;
|
|
||||||
},
|
|
||||||
) => {
|
|
||||||
const result = await db
|
|
||||||
.from("list")
|
|
||||||
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
|
||||||
.eq("id", args.listId)
|
|
||||||
.is("deletedAt", null);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const reorder = async (
|
export const reorder = async (
|
||||||
db: SupabaseClient<Database>,
|
db: SupabaseClient<Database>,
|
||||||
args: {
|
args: {
|
||||||
@@ -152,3 +118,37 @@ export const shiftIndex = async (
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const softDeleteAllByBoardId = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
args: {
|
||||||
|
boardId: number;
|
||||||
|
deletedAt: string;
|
||||||
|
deletedBy: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const result = await db
|
||||||
|
.from("list")
|
||||||
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
|
.eq("boardId", args.boardId)
|
||||||
|
.is("deletedAt", null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const softDeleteById = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
args: {
|
||||||
|
listId: number;
|
||||||
|
deletedAt: string;
|
||||||
|
deletedBy: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const result = await db
|
||||||
|
.from("list")
|
||||||
|
.update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy })
|
||||||
|
.eq("id", args.listId)
|
||||||
|
.is("deletedAt", null);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -127,3 +127,15 @@ export const getAllMembersByPublicIds = async (
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const hardDelete = async (
|
||||||
|
db: SupabaseClient<Database>,
|
||||||
|
workspacePublicId: string,
|
||||||
|
) => {
|
||||||
|
const result = db
|
||||||
|
.from("workspace")
|
||||||
|
.delete()
|
||||||
|
.eq("publicId", workspacePublicId);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,217 +12,237 @@ import {
|
|||||||
bigint,
|
bigint,
|
||||||
} from "drizzle-orm/pg-core";
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
export const importSourceEnum = pgEnum('source', ['trello']);
|
export const importSourceEnum = pgEnum("source", ["trello"]);
|
||||||
export const importStatusEnum = pgEnum('status', ['started', 'success', 'failed']);
|
export const importStatusEnum = pgEnum("status", [
|
||||||
export const memberRoleEnum = pgEnum('role', ['admin', 'member', 'guest']);
|
"started",
|
||||||
|
"success",
|
||||||
|
"failed",
|
||||||
|
]);
|
||||||
|
export const memberRoleEnum = pgEnum("role", ["admin", "member", "guest"]);
|
||||||
|
|
||||||
export const boards = pgTable(
|
export const boards = pgTable("board", {
|
||||||
"board",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
createdBy: uuid("createdBy")
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
.references(() => users.id),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updatedAt"),
|
updatedAt: timestamp("updatedAt"),
|
||||||
deletedAt: timestamp("deletedAt"),
|
deletedAt: timestamp("deletedAt"),
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
workspaceId: bigint("workspaceId", { mode: "number" }).notNull().references(() => workspaces.id),
|
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||||
},
|
.notNull()
|
||||||
);
|
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||||
|
});
|
||||||
|
|
||||||
export const boardsRelations = relations(boards, ({ one, many }) => ({
|
export const boardsRelations = relations(boards, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
fields: [boards.createdBy],
|
fields: [boards.createdBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
lists: many(lists),
|
lists: many(lists),
|
||||||
labels: many(labels),
|
labels: many(labels),
|
||||||
deletedBy: one(users, {
|
deletedBy: one(users, {
|
||||||
fields: [boards.deletedBy],
|
fields: [boards.deletedBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
import: one(imports, {
|
import: one(imports, {
|
||||||
fields: [boards.importId],
|
fields: [boards.importId],
|
||||||
references: [imports.id],
|
references: [imports.id],
|
||||||
}),
|
}),
|
||||||
workspace: one(workspaces, {
|
workspace: one(workspaces, {
|
||||||
fields: [boards.workspaceId],
|
fields: [boards.workspaceId],
|
||||||
references: [workspaces.id],
|
references: [workspaces.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const imports = pgTable(
|
export const imports = pgTable("import", {
|
||||||
"import",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
source: importSourceEnum("source").notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
status: importStatusEnum("status").notNull(),
|
||||||
source: importSourceEnum('source').notNull(),
|
createdBy: uuid("createdBy")
|
||||||
status: importStatusEnum('status').notNull(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
.references(() => users.id),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const importsRelations = relations(imports, ({ one, many }) => ({
|
export const importsRelations = relations(imports, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
fields: [imports.createdBy],
|
fields: [imports.createdBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
boards: many(boards),
|
boards: many(boards),
|
||||||
cards: many(cards),
|
cards: many(cards),
|
||||||
lists: many(lists),
|
lists: many(lists),
|
||||||
labels: many(labels)
|
labels: many(labels),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const labels = pgTable(
|
export const labels = pgTable("label", {
|
||||||
"label",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
colourCode: varchar("colourCode", { length: 12 }),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
createdBy: uuid("createdBy")
|
||||||
colourCode: varchar("colourCode", { length: 12 }),
|
.notNull()
|
||||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
.references(() => users.id),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updatedAt"),
|
updatedAt: timestamp("updatedAt"),
|
||||||
boardId: bigint("boardId", { mode: "number" }).notNull().references(() => boards.id),
|
boardId: bigint("boardId", { mode: "number" })
|
||||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
.notNull()
|
||||||
}
|
.references(() => boards.id, { onDelete: "cascade" }),
|
||||||
);
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
|
});
|
||||||
|
|
||||||
export const labelsRelations = relations(labels, ({ one, many }) => ({
|
export const labelsRelations = relations(labels, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
fields: [labels.createdBy],
|
fields: [labels.createdBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
board: one(boards, {
|
board: one(boards, {
|
||||||
fields: [labels.boardId],
|
fields: [labels.boardId],
|
||||||
references: [boards.id],
|
references: [boards.id],
|
||||||
}),
|
}),
|
||||||
cards: many(cardsToLabels),
|
cards: many(cardsToLabels),
|
||||||
import: one(imports, {
|
import: one(imports, {
|
||||||
fields: [labels.importId],
|
fields: [labels.importId],
|
||||||
references: [imports.id],
|
references: [imports.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const cardsToLabels = pgTable(
|
export const cardsToLabels = pgTable(
|
||||||
"_card_labels",
|
"_card_labels",
|
||||||
{
|
{
|
||||||
cardId: bigint("cardId", { mode: "number" }).notNull().references(() => cards.id),
|
cardId: bigint("cardId", { mode: "number" })
|
||||||
labelId: bigint("labelId", { mode: "number" }).notNull().references(() => labels.id),
|
.notNull()
|
||||||
}, (t) => ({
|
.references(() => cards.id),
|
||||||
|
labelId: bigint("labelId", { mode: "number" })
|
||||||
|
.notNull()
|
||||||
|
.references(() => labels.id, { onDelete: "cascade" }),
|
||||||
|
},
|
||||||
|
(t) => ({
|
||||||
pk: primaryKey(t.cardId, t.labelId),
|
pk: primaryKey(t.cardId, t.labelId),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
|
export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({
|
||||||
card: one(cards, {
|
card: one(cards, {
|
||||||
fields: [cardsToLabels.cardId],
|
fields: [cardsToLabels.cardId],
|
||||||
references: [cards.id],
|
references: [cards.id],
|
||||||
}),
|
}),
|
||||||
label: one(labels, {
|
label: one(labels, {
|
||||||
fields: [cardsToLabels.labelId],
|
fields: [cardsToLabels.labelId],
|
||||||
references: [labels.id],
|
references: [labels.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const cardToWorkspaceMembers = pgTable(
|
export const cardToWorkspaceMembers = pgTable(
|
||||||
"_card_workspace_members",
|
"_card_workspace_members",
|
||||||
{
|
{
|
||||||
cardId: bigint("cardId", { mode: "number" }).notNull().references(() => cards.id),
|
cardId: bigint("cardId", { mode: "number" })
|
||||||
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }).notNull().references(() => workspaceMembers.id),
|
.notNull()
|
||||||
}, (t) => ({
|
.references(() => cards.id),
|
||||||
|
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" })
|
||||||
|
.notNull()
|
||||||
|
.references(() => workspaceMembers.id, { onDelete: "cascade" }),
|
||||||
|
},
|
||||||
|
(t) => ({
|
||||||
pk: primaryKey(t.cardId, t.workspaceMemberId),
|
pk: primaryKey(t.cardId, t.workspaceMemberId),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const cardToWorkspaceMembersRelations = relations(cardToWorkspaceMembers, ({ one }) => ({
|
export const cardToWorkspaceMembersRelations = relations(
|
||||||
card: one(cards, {
|
cardToWorkspaceMembers,
|
||||||
fields: [cardToWorkspaceMembers.cardId],
|
({ one }) => ({
|
||||||
references: [cards.id],
|
card: one(cards, {
|
||||||
}),
|
fields: [cardToWorkspaceMembers.cardId],
|
||||||
member: one(workspaceMembers, {
|
references: [cards.id],
|
||||||
fields: [cardToWorkspaceMembers.workspaceMemberId],
|
}),
|
||||||
references: [workspaceMembers.id],
|
member: one(workspaceMembers, {
|
||||||
}),
|
fields: [cardToWorkspaceMembers.workspaceMemberId],
|
||||||
}));
|
references: [workspaceMembers.id],
|
||||||
|
}),
|
||||||
export const lists = pgTable(
|
}),
|
||||||
"list",
|
|
||||||
{
|
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
|
||||||
index: integer("index").notNull(),
|
|
||||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
|
||||||
updatedAt: timestamp("updatedAt"),
|
|
||||||
deletedAt: timestamp("deletedAt"),
|
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
|
||||||
boardId: bigint("boardId", { mode: "number" }).notNull().references(() => boards.id),
|
|
||||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const lists = pgTable("list", {
|
||||||
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
|
index: integer("index").notNull(),
|
||||||
|
createdBy: uuid("createdBy")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt"),
|
||||||
|
deletedAt: timestamp("deletedAt"),
|
||||||
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
|
boardId: bigint("boardId", { mode: "number" })
|
||||||
|
.notNull()
|
||||||
|
.references(() => boards.id, { onDelete: "cascade" }),
|
||||||
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
|
});
|
||||||
|
|
||||||
export const listsRelations = relations(lists, ({ one, many }) => ({
|
export const listsRelations = relations(lists, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
fields: [lists.createdBy],
|
fields: [lists.createdBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
board: one(boards, {
|
board: one(boards, {
|
||||||
fields: [lists.boardId],
|
fields: [lists.boardId],
|
||||||
references: [boards.id],
|
references: [boards.id],
|
||||||
}),
|
}),
|
||||||
cards: many(cards),
|
cards: many(cards),
|
||||||
deletedBy: one(users, {
|
deletedBy: one(users, {
|
||||||
fields: [lists.deletedBy],
|
fields: [lists.deletedBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
import: one(imports, {
|
import: one(imports, {
|
||||||
fields: [lists.importId],
|
fields: [lists.importId],
|
||||||
references: [imports.id],
|
references: [imports.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const cards = pgTable(
|
export const cards = pgTable("card", {
|
||||||
"card",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
title: varchar("title", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
description: text("description"),
|
||||||
title: varchar("title", { length: 255 }).notNull(),
|
index: integer("index").notNull(),
|
||||||
description: text("description"),
|
createdBy: uuid("createdBy")
|
||||||
index: integer("index").notNull(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
.references(() => users.id),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updatedAt"),
|
updatedAt: timestamp("updatedAt"),
|
||||||
deletedAt: timestamp("deletedAt"),
|
deletedAt: timestamp("deletedAt"),
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
listId: bigint("listId", { mode: "number" }).notNull().references(() => lists.id),
|
listId: bigint("listId", { mode: "number" })
|
||||||
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
.notNull()
|
||||||
}
|
.references(() => lists.id, { onDelete: "cascade" }),
|
||||||
);
|
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
|
||||||
|
});
|
||||||
|
|
||||||
export const cardsRelations = relations(cards, ({ one, many }) => ({
|
export const cardsRelations = relations(cards, ({ one, many }) => ({
|
||||||
createdBy: one(users, {
|
createdBy: one(users, {
|
||||||
fields: [cards.createdBy],
|
fields: [cards.createdBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
list: one(lists, {
|
list: one(lists, {
|
||||||
fields: [cards.listId],
|
fields: [cards.listId],
|
||||||
references: [lists.id],
|
references: [lists.id],
|
||||||
}),
|
}),
|
||||||
deletedBy: one(users, {
|
deletedBy: one(users, {
|
||||||
fields: [cards.deletedBy],
|
fields: [cards.deletedBy],
|
||||||
references: [users.id],
|
references: [users.id],
|
||||||
}),
|
}),
|
||||||
labels: many(cardsToLabels),
|
labels: many(cardsToLabels),
|
||||||
members: many(cardToWorkspaceMembers),
|
members: many(cardToWorkspaceMembers),
|
||||||
import: one(imports, {
|
import: one(imports, {
|
||||||
fields: [cards.importId],
|
fields: [cards.importId],
|
||||||
references: [imports.id],
|
references: [imports.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const users = pgTable("user", {
|
export const users = pgTable("user", {
|
||||||
@@ -241,51 +261,55 @@ export const usersRelations = relations(users, ({ many }) => ({
|
|||||||
workspaces: many(workspaces),
|
workspaces: many(workspaces),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const workspaces = pgTable(
|
export const workspaces = pgTable("workspace", {
|
||||||
"workspace",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
name: varchar("name", { length: 255 }).notNull(),
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
createdBy: uuid("createdBy")
|
||||||
slug: varchar("slug", { length: 255 }).notNull().unique(),
|
.notNull()
|
||||||
createdBy: uuid("createdBy").notNull().references(() => users.id),
|
.references(() => users.id),
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
updatedAt: timestamp("updatedAt"),
|
updatedAt: timestamp("updatedAt"),
|
||||||
deletedAt: timestamp("deletedAt"),
|
deletedAt: timestamp("deletedAt"),
|
||||||
deletedBy: uuid("deletedBy").references(() => users.id),
|
deletedBy: uuid("deletedBy").references(() => users.id),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
||||||
user: one(users, { fields: [workspaces.createdBy], references: [users.id] }),
|
user: one(users, { fields: [workspaces.createdBy], references: [users.id] }),
|
||||||
members: many(workspaceMembers)
|
members: many(workspaceMembers),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const workspaceMembers = pgTable(
|
export const workspaceMembers = pgTable("workspace_members", {
|
||||||
"workspace_members",
|
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||||
{
|
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
userId: uuid("userId")
|
||||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
.notNull()
|
||||||
userId: uuid("userId").notNull().references(() => users.id),
|
.references(() => users.id),
|
||||||
workspaceId: bigint("workspaceId", { mode: "number" }).notNull().references(() => workspaces.id),
|
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||||
createdBy: uuid("createdBy").notNull(),
|
.notNull()
|
||||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||||
updatedAt: timestamp("updatedAt"),
|
createdBy: uuid("createdBy").notNull(),
|
||||||
deletedAt: timestamp("deletedAt"),
|
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||||
role: memberRoleEnum('role').notNull(),
|
updatedAt: timestamp("updatedAt"),
|
||||||
},
|
deletedAt: timestamp("deletedAt"),
|
||||||
|
role: memberRoleEnum("role").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const usersToWorkspacesRelations = relations(
|
||||||
|
workspaceMembers,
|
||||||
|
({ one }) => ({
|
||||||
|
addedBy: one(users, {
|
||||||
|
fields: [workspaceMembers.createdBy],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
user: one(users, {
|
||||||
|
fields: [workspaceMembers.userId],
|
||||||
|
references: [users.id],
|
||||||
|
}),
|
||||||
|
workspace: one(workspaces, {
|
||||||
|
fields: [workspaceMembers.workspaceId],
|
||||||
|
references: [workspaces.id],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const usersToWorkspacesRelations = relations(workspaceMembers, ({ one }) => ({
|
|
||||||
addedBy: one(users, { fields: [workspaceMembers.createdBy], references: [users.id] }),
|
|
||||||
user: one(users, {
|
|
||||||
fields: [workspaceMembers.userId],
|
|
||||||
references: [users.id],
|
|
||||||
}),
|
|
||||||
workspace: one(workspaces, {
|
|
||||||
fields: [workspaceMembers.workspaceId],
|
|
||||||
references: [workspaces.id],
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
import { api } from "~/utils/api";
|
||||||
|
import { useModal } from "~/providers/modal";
|
||||||
|
import { usePopup } from "~/providers/popup";
|
||||||
|
import { useWorkspace } from "~/providers/workspace";
|
||||||
|
|
||||||
|
import Button from "~/components/Button";
|
||||||
|
|
||||||
|
export function DeleteWorkspaceConfirmation() {
|
||||||
|
const { closeModal } = useModal();
|
||||||
|
const { workspace, switchWorkspace, availableWorkspaces } = useWorkspace();
|
||||||
|
const { showPopup } = usePopup();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [isAcknowledgmentChecked, setIsAcknowledgmentChecked] = useState(false);
|
||||||
|
|
||||||
|
const deleteWorkspaceMutation = api.workspace.delete.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
closeModal();
|
||||||
|
const filteredWorkspaces = availableWorkspaces.filter(
|
||||||
|
(ws) => ws.publicId !== workspace?.publicId,
|
||||||
|
);
|
||||||
|
if (filteredWorkspaces.length > 0 && filteredWorkspaces[0]) {
|
||||||
|
switchWorkspace(filteredWorkspaces[0]);
|
||||||
|
} else {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
closeModal();
|
||||||
|
showPopup({
|
||||||
|
header: "Error deleting workspace",
|
||||||
|
message: "Please try again later, or contact customer support.",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDeleteWorkspace = () => {
|
||||||
|
deleteWorkspaceMutation.mutate({
|
||||||
|
workspacePublicId: workspace?.publicId,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-5">
|
||||||
|
<div className="flex w-full flex-col justify-between pb-4">
|
||||||
|
<h2 className="text-md pb-4 font-medium text-neutral-900 dark:text-dark-1000">
|
||||||
|
{`Are you sure you want to delete the workspace ${workspace?.name}?`}
|
||||||
|
</h2>
|
||||||
|
<p className="mb-4 text-sm text-light-900 dark:text-dark-900">
|
||||||
|
Keep in mind that this action is irreversible.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-light-900 dark:text-dark-900">
|
||||||
|
This will result in the permanent deletion of all data associated with
|
||||||
|
this workspace.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex items-start">
|
||||||
|
<div className="flex h-6 items-center">
|
||||||
|
<input
|
||||||
|
id="acknowledgment"
|
||||||
|
name="acknowledgment"
|
||||||
|
type="checkbox"
|
||||||
|
aria-describedby="acknowledgment-description"
|
||||||
|
className="mt-2 h-[14px] w-[14px] rounded border-gray-300 bg-transparent text-indigo-600 focus:shadow-none focus:ring-0 focus:ring-offset-0"
|
||||||
|
checked={isAcknowledgmentChecked}
|
||||||
|
onChange={() =>
|
||||||
|
setIsAcknowledgmentChecked(!isAcknowledgmentChecked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm leading-6">
|
||||||
|
<p id="comments-description" className="text-dark-1000">
|
||||||
|
I acknowledge that all of the workspace data will be permanently
|
||||||
|
deleted and want to proceed.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5 flex justify-end space-x-2 sm:mt-6">
|
||||||
|
<Button variant="secondary" onClick={() => closeModal()}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={handleDeleteWorkspace}
|
||||||
|
disabled={!isAcknowledgmentChecked}
|
||||||
|
isLoading={deleteWorkspaceMutation.isPending}
|
||||||
|
>
|
||||||
|
Delete workspace
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import Modal from "~/components/modal";
|
import Modal from "~/components/modal";
|
||||||
|
import Button from "~/components/Button";
|
||||||
import { NewWorkspaceForm } from "~/components/NewWorkspaceForm";
|
import { DeleteWorkspaceConfirmation } from "./components/DeleteWorkspaceConfirmation";
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const { modalContentType } = useModal();
|
const { modalContentType, openModal } = useModal();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-28 py-12">
|
<div className="px-28 py-12">
|
||||||
@@ -14,16 +14,23 @@ export default function SettingsPage() {
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 flow-root">
|
<div className="border-t border-light-300 dark:border-dark-300">
|
||||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
<h2 className="mt-8 text-[14px] text-neutral-900 dark:text-dark-1000">
|
||||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
Delete workspace
|
||||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg"></div>
|
</h2>
|
||||||
</div>
|
<p className="mb-8 mt-2 text-sm text-neutral-500 dark:text-dark-900">
|
||||||
</div>
|
Once you delete your workspace, there is no going back. Please be
|
||||||
|
certain.
|
||||||
|
</p>
|
||||||
|
<Button variant="primary" onClick={() => openModal("DELETE_WORKSPACE")}>
|
||||||
|
Delete workspace
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal>
|
<Modal>
|
||||||
{modalContentType === "NEW_WORKSPACE" && <NewWorkspaceForm />}
|
{modalContentType === "DELETE_WORKSPACE" && (
|
||||||
|
<DeleteWorkspaceConfirmation />
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -207,10 +207,25 @@ USING (
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE POLICY "Allow access to user's workspaces"
|
CREATE POLICY "Allow viewing user's workspaces"
|
||||||
ON public.workspace
|
ON public.workspace
|
||||||
AS PERMISSIVE
|
AS PERMISSIVE
|
||||||
FOR ALL
|
FOR SELECT
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
id IN (
|
||||||
|
SELECT "workspaceId"
|
||||||
|
FROM workspace_members
|
||||||
|
WHERE "userId" = auth.uid()
|
||||||
|
)
|
||||||
|
OR
|
||||||
|
"createdBy" = auth.uid()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Allow updating user's workspaces"
|
||||||
|
ON public.workspace
|
||||||
|
AS PERMISSIVE
|
||||||
|
FOR UPDATE
|
||||||
TO authenticated
|
TO authenticated
|
||||||
USING (
|
USING (
|
||||||
id IN (
|
id IN (
|
||||||
@@ -220,6 +235,26 @@ USING (
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Allow deleting user's workspaces"
|
||||||
|
ON public.workspace
|
||||||
|
AS PERMISSIVE
|
||||||
|
FOR DELETE
|
||||||
|
TO authenticated
|
||||||
|
USING (
|
||||||
|
id IN (
|
||||||
|
SELECT "workspaceId"
|
||||||
|
FROM workspace_members
|
||||||
|
WHERE "userId" = auth.uid()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE POLICY "Allow authenticated users to create workspaces"
|
||||||
|
ON public.workspace
|
||||||
|
AS PERMISSIVE
|
||||||
|
FOR INSERT
|
||||||
|
TO authenticated
|
||||||
|
USING (true);
|
||||||
|
|
||||||
CREATE POLICY "Allow access to user's own workspace membership"
|
CREATE POLICY "Allow access to user's own workspace membership"
|
||||||
ON public.workspace_members
|
ON public.workspace_members
|
||||||
AS PERMISSIVE
|
AS PERMISSIVE
|
||||||
|
|||||||
Reference in New Issue
Block a user