mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
import type { FileUpload } from "@remix-run/form-data-parser";
|
|
import { nanoid } from "nanoid";
|
|
import type { ActionFunction } from "react-router";
|
|
import { redirect } from "react-router";
|
|
import * as ArtRepository from "~/features/art/ArtRepository.server";
|
|
import { requireUser } from "~/features/auth/core/user.server";
|
|
import { uploadStreamToS3 } from "~/features/img-upload/s3.server";
|
|
import { notify } from "~/features/notifications/core/notify.server";
|
|
import { requireRole } from "~/modules/permissions/guards.server";
|
|
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
|
import invariant from "~/utils/invariant";
|
|
import {
|
|
errorToastIfFalsy,
|
|
parseFormData,
|
|
parseRequestPayload,
|
|
safeParseMultipartFormData,
|
|
} from "~/utils/remix.server";
|
|
import { userArtPage } from "~/utils/urls";
|
|
import { NEW_ART_EXISTING_SEARCH_PARAM_KEY } from "../art-constants";
|
|
import { editArtSchema, newArtSchema } from "../art-schemas.server";
|
|
|
|
export const action: ActionFunction = async ({ request }) => {
|
|
const user = requireUser();
|
|
requireRole(user, "ARTIST");
|
|
|
|
const searchParams = new URL(request.url).searchParams;
|
|
const artIdRaw = searchParams.get(NEW_ART_EXISTING_SEARCH_PARAM_KEY);
|
|
|
|
// updating logic
|
|
if (artIdRaw) {
|
|
const artId = Number(artIdRaw);
|
|
|
|
const userArts = await ArtRepository.findArtsByUserId(user.id, {
|
|
includeTagged: false,
|
|
});
|
|
const existingArt = userArts.find((art) => art.id === artId);
|
|
errorToastIfFalsy(existingArt, "Art author is someone else");
|
|
|
|
const data = await parseRequestPayload({
|
|
request,
|
|
schema: editArtSchema,
|
|
});
|
|
|
|
const editedArtId = await ArtRepository.update(artId, {
|
|
description: data.description,
|
|
isShowcase: data.isShowcase,
|
|
linkedUsers: data.linkedUsers,
|
|
tags: data.tags,
|
|
});
|
|
|
|
const existingLinkedUserIds =
|
|
existingArt.linkedUsers?.map((u) => u.id) ?? [];
|
|
const newLinkedUsers = data.linkedUsers.filter(
|
|
(userId) => !existingLinkedUserIds.includes(userId),
|
|
);
|
|
|
|
notify({
|
|
userIds: newLinkedUsers,
|
|
notification: {
|
|
type: "TAGGED_TO_ART",
|
|
meta: {
|
|
adderUsername: user.username,
|
|
adderDiscordId: user.discordId,
|
|
artId: editedArtId,
|
|
},
|
|
},
|
|
});
|
|
} else {
|
|
const preDecidedFilename = `art-${nanoid()}-${Date.now()}`;
|
|
|
|
const uploadHandler = async (fileUpload: FileUpload) => {
|
|
if (
|
|
fileUpload.fieldName === "img" ||
|
|
fileUpload.fieldName === "smallImg"
|
|
) {
|
|
const [, ending] = fileUpload.name.split(".");
|
|
invariant(ending);
|
|
const newFilename = `${preDecidedFilename}${fileUpload.fieldName === "smallImg" ? "-small" : ""}.${ending}`;
|
|
|
|
const uploadedFileLocation = await uploadStreamToS3(
|
|
fileUpload.stream(),
|
|
newFilename,
|
|
);
|
|
return uploadedFileLocation;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const formData = await safeParseMultipartFormData(
|
|
request,
|
|
// 5MB
|
|
{ maxFileSize: 5 * 1024 * 1024 },
|
|
uploadHandler,
|
|
);
|
|
const imgSrc = formData.get("img") as string | null;
|
|
invariant(imgSrc);
|
|
|
|
const urlParts = imgSrc.split("/");
|
|
const fileName = urlParts[urlParts.length - 1];
|
|
invariant(fileName);
|
|
|
|
const data = await parseFormData({
|
|
formData,
|
|
schema: newArtSchema,
|
|
});
|
|
|
|
const addedArt = await ArtRepository.insert({
|
|
authorId: user.id,
|
|
description: data.description,
|
|
url: fileName,
|
|
validatedAt: user.patronTier ? dateToDatabaseTimestamp(new Date()) : null,
|
|
linkedUsers: data.linkedUsers,
|
|
tags: data.tags,
|
|
});
|
|
|
|
notify({
|
|
userIds: data.linkedUsers,
|
|
notification: {
|
|
type: "TAGGED_TO_ART",
|
|
meta: {
|
|
adderUsername: user.username,
|
|
adderDiscordId: user.discordId,
|
|
artId: addedArt.id,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
throw redirect(userArtPage(user));
|
|
};
|