mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Removing art from user profile by user feature
This commit is contained in:
parent
7c470f6cc2
commit
4e7d453e8d
15
app/features/art/ArtRepository.server.ts
Normal file
15
app/features/art/ArtRepository.server.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { db } from "~/db/sql";
|
||||
|
||||
export function unlinkUserFromArt({
|
||||
userId,
|
||||
artId,
|
||||
}: {
|
||||
userId: number;
|
||||
artId: number;
|
||||
}) {
|
||||
return db
|
||||
.deleteFrom("ArtUserMetadata")
|
||||
.where("artId", "=", artId)
|
||||
.where("userId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { z } from "zod";
|
||||
import {
|
||||
_action,
|
||||
checkboxValueToDbBoolean,
|
||||
dbBoolean,
|
||||
falsyToNull,
|
||||
|
|
@ -43,5 +44,16 @@ export const editArtSchema = z.object({
|
|||
});
|
||||
|
||||
export const deleteArtSchema = z.object({
|
||||
_action: _action("DELETE_ART"),
|
||||
id,
|
||||
});
|
||||
|
||||
export const unlinkArtSchema = z.object({
|
||||
_action: _action("UNLINK_ART"),
|
||||
id,
|
||||
});
|
||||
|
||||
export const userArtPageActionSchema = z.union([
|
||||
deleteArtSchema,
|
||||
unlinkArtSchema,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { FormWithConfirm } from "~/components/FormWithConfirm";
|
|||
import { Pagination } from "~/components/Pagination";
|
||||
import { EditIcon } from "~/components/icons/Edit";
|
||||
import { TrashIcon } from "~/components/icons/Trash";
|
||||
import { UnlinkIcon } from "~/components/icons/Unlink";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { usePagination } from "~/hooks/usePagination";
|
||||
import { useSearchParamState } from "~/hooks/useSearchParamState";
|
||||
|
|
@ -191,8 +192,11 @@ function ImagePreview({
|
|||
{t("common:actions.edit")}
|
||||
</LinkButton>
|
||||
<FormWithConfirm
|
||||
dialogHeading="Are you sure you want to delete the art?"
|
||||
fields={[["id", art.id]]}
|
||||
dialogHeading={t("art:delete.title")}
|
||||
fields={[
|
||||
["id", art.id],
|
||||
["_action", "DELETE_ART"],
|
||||
]}
|
||||
>
|
||||
<Button icon={<TrashIcon />} variant="destructive" size="tiny" />
|
||||
</FormWithConfirm>
|
||||
|
|
@ -207,15 +211,35 @@ function ImagePreview({
|
|||
return (
|
||||
<div>
|
||||
{img}
|
||||
<Link
|
||||
to={userArtPage(art.author, "MADE-BY")}
|
||||
className={clsx("stack sm horizontal text-xs items-center mt-1", {
|
||||
invisible: !imageLoaded,
|
||||
<div
|
||||
className={clsx("stack horizontal justify-between", {
|
||||
"mt-2": canEdit,
|
||||
})}
|
||||
>
|
||||
<Avatar user={art.author} size="xxs" />
|
||||
{t("art:madeBy")} {art.author.username}
|
||||
</Link>
|
||||
<Link
|
||||
to={userArtPage(art.author, "MADE-BY")}
|
||||
className={clsx("stack sm horizontal text-xs items-center mt-1", {
|
||||
invisible: !imageLoaded,
|
||||
})}
|
||||
>
|
||||
<Avatar user={art.author} size="xxs" />
|
||||
{t("art:madeBy")} {art.author.username}
|
||||
</Link>
|
||||
{canEdit ? (
|
||||
<FormWithConfirm
|
||||
dialogHeading={t("art:unlink.title", {
|
||||
username: art.author.username,
|
||||
})}
|
||||
fields={[
|
||||
["id", art.id],
|
||||
["_action", "UNLINK_ART"],
|
||||
]}
|
||||
deleteButtonText={t("common:actions.remove")}
|
||||
>
|
||||
<Button icon={<UnlinkIcon />} variant="destructive" size="tiny" />
|
||||
</FormWithConfirm>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,54 @@
|
|||
import type { ActionFunction } from "@remix-run/node";
|
||||
import { deleteArtSchema } from "~/features/art/art-schemas.server";
|
||||
import * as ArtRepository from "~/features/art/ArtRepository.server";
|
||||
import { userArtPageActionSchema } from "~/features/art/art-schemas.server";
|
||||
import { deleteArt } from "~/features/art/queries/deleteArt.server";
|
||||
import { findArtById } from "~/features/art/queries/findArtById.server";
|
||||
import { requireUserId } from "~/features/auth/core/user.server";
|
||||
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
|
||||
import { logger } from "~/utils/logger";
|
||||
import {
|
||||
errorToastIfFalsy,
|
||||
parseRequestPayload,
|
||||
successToast,
|
||||
} from "~/utils/remix.server";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
|
||||
export const action: ActionFunction = async ({ request }) => {
|
||||
const user = await requireUserId(request);
|
||||
const data = await parseRequestPayload({
|
||||
request,
|
||||
schema: deleteArtSchema,
|
||||
schema: userArtPageActionSchema,
|
||||
});
|
||||
|
||||
// this actually doesn't delete the image itself from the static hosting
|
||||
// but the idea is that storage is cheap anyway and if needed later
|
||||
// then we can have a routine that checks all the images still current and nukes the rest
|
||||
const artToDelete = findArtById(data.id);
|
||||
errorToastIfFalsy(
|
||||
artToDelete?.authorId === user.id,
|
||||
"Insufficient permissions",
|
||||
);
|
||||
switch (data._action) {
|
||||
case "DELETE_ART": {
|
||||
// this actually doesn't delete the image itself from the static hosting
|
||||
// but the idea is that storage is cheap anyway and if needed later
|
||||
// then we can have a routine that checks all the images still current and nukes the rest
|
||||
const artToDelete = findArtById(data.id);
|
||||
errorToastIfFalsy(
|
||||
artToDelete?.authorId === user.id,
|
||||
"Insufficient permissions",
|
||||
);
|
||||
|
||||
deleteArt(data.id);
|
||||
deleteArt(data.id);
|
||||
|
||||
return null;
|
||||
return successToast("Deleting art successful");
|
||||
}
|
||||
case "UNLINK_ART": {
|
||||
logger.info("Unlinking art", {
|
||||
userId: user.id,
|
||||
artId: data.id,
|
||||
});
|
||||
|
||||
await ArtRepository.unlinkUserFromArt({
|
||||
userId: user.id,
|
||||
artId: data.id,
|
||||
});
|
||||
|
||||
return successToast("Unlinking art successful");
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,5 +24,8 @@
|
|||
"forms.tags.addNew": "Create a new one.",
|
||||
"forms.tags.addNew.placeholder": "Create a new tag",
|
||||
"forms.tags.searchExisting.placeholder": "Search existing tags",
|
||||
"forms.tags.maxReached": "Max tags reached"
|
||||
"forms.tags.maxReached": "Max tags reached",
|
||||
|
||||
"delete.title": "Are you sure you want to delete the art?",
|
||||
"unlink.title": "Are you sure you want to remove this art from your profile (only {{username}} can add it back)?"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user