mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-05-13 13:17:45 +00:00
fix: can't edit custom images properly
also cleanup
This commit is contained in:
parent
a5080f1b2e
commit
913f0ef65a
5 changed files with 35 additions and 53 deletions
|
|
@ -80,6 +80,8 @@ For Discord, create an application in the developer portal, go to 'OAuth2', copy
|
|||
|
||||
For GitHub, navigate to your profile settings, then 'Developer Settings', and create a new application. Set the homepage URL to `http://localhost:3000` and copy the Client ID and generate a new client secret. Finally, add in a callback URL with the value `http://localhost:3000/api/auth/callback/github`.
|
||||
|
||||
Google is annoying so I'm not explaining it.
|
||||
|
||||
After configuring the environment variables, you can run a development server.
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -110,26 +110,28 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
parsed.data;
|
||||
|
||||
// Validate image files
|
||||
let wasImagesModerated = false;
|
||||
const images: File[] = [];
|
||||
const customImages: File[] = [];
|
||||
|
||||
for (const img of [image1, image2, image3]) {
|
||||
if (!img) continue;
|
||||
|
||||
const validation = await validateImage(img);
|
||||
if (!validation.valid) wasImagesModerated = true;
|
||||
images.push(img);
|
||||
if (validation.valid) {
|
||||
customImages.push(img);
|
||||
} else {
|
||||
return rateLimit.sendResponse({ error: `Failed to verify custom image: ${validation.error}` }, validation.status ?? 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Mii portrait & features image (Switch)
|
||||
if (mii.platform === "SWITCH") {
|
||||
if (miiPortraitImage) {
|
||||
const validation = await validateImage(miiPortraitImage);
|
||||
if (!validation.valid) wasImagesModerated = true;
|
||||
if (!validation.valid) return rateLimit.sendResponse({ error: `Failed to verify portrait: ${validation.error}` }, validation.status ?? 400);
|
||||
}
|
||||
if (miiFeaturesImage) {
|
||||
const validation = await validateImage(miiFeaturesImage);
|
||||
if (!validation.valid) wasImagesModerated = true;
|
||||
if (!validation.valid) return rateLimit.sendResponse({ error: `Failed to verify features: ${validation.error}` }, validation.status ?? 400);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,10 +149,10 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
if (makeup !== undefined) updateData.makeup = makeup;
|
||||
if (youtubeId !== undefined) updateData.youtubeId = youtubeId;
|
||||
if (instructions !== undefined) updateData.instructions = instructions;
|
||||
if (images.length > 0) updateData.imageCount = images.length;
|
||||
if (customImages.length > 0) updateData.imageCount = customImages.length;
|
||||
|
||||
const imagesChanged = images.length > 0 || miiPortraitImage || miiFeaturesImage;
|
||||
if ((settings.queueEnabled && imagesChanged) || wasImagesModerated) updateData.in_queue = true;
|
||||
const imagesChanged = customImages.length > 0 || miiPortraitImage || miiFeaturesImage;
|
||||
if (settings.queueEnabled && imagesChanged) updateData.in_queue = true;
|
||||
|
||||
if (Object.keys(updateData).length === 0) return rateLimit.sendResponse({ error: "Nothing was changed" }, 400);
|
||||
const updatedMii = await prisma.mii.update({
|
||||
|
|
@ -172,7 +174,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
await fs.mkdir(miiUploadsDirectory, { recursive: true });
|
||||
|
||||
// Only touch files if new images were uploaded
|
||||
if (images.length > 0) {
|
||||
if (customImages.length > 0) {
|
||||
// Delete all custom images
|
||||
const files = await fs.readdir(miiUploadsDirectory);
|
||||
await Promise.all(files.filter((file) => file.startsWith("image")).map((file) => fs.unlink(path.join(miiUploadsDirectory, file))));
|
||||
|
|
@ -180,7 +182,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
// Compress and upload new images
|
||||
try {
|
||||
await Promise.all(
|
||||
images.map(async (image, index) => {
|
||||
customImages.map(async (image, index) => {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const pngBuffer = await sharp(buffer).resize({ height: 800, fit: "inside", withoutEnlargement: true }).png({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(miiUploadsDirectory, `image${index}.png`);
|
||||
|
|
@ -198,17 +200,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
// Only save portrait & features for Switch Miis when they are provided
|
||||
if (mii.platform === "SWITCH" && (miiPortraitImage || miiFeaturesImage)) {
|
||||
try {
|
||||
// Delete existing portrait/features if they're being replaced
|
||||
await Promise.all(
|
||||
["mii.png", "features.png"]
|
||||
.filter((file) => {
|
||||
if (file === "mii.png") return miiPortraitImage;
|
||||
if (file === "features.png") return miiFeaturesImage;
|
||||
return false;
|
||||
})
|
||||
.map((file) => fs.unlink(path.join(miiUploadsDirectory, file))),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
miiPortraitImage &&
|
||||
|
|
|
|||
|
|
@ -63,13 +63,13 @@ const submitSchema = z
|
|||
(data) => {
|
||||
// If platform is Switch, gender, miiPortraitImage, and miiFeaturesImage must be present
|
||||
if (data.platform === "SWITCH") {
|
||||
return data.gender !== undefined && data.miiPortraitImage !== undefined;
|
||||
return data.gender !== undefined && data.miiPortraitImage !== undefined && data.miiFeaturesImage !== undefined;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Gender, Mii portrait & features image, and instructions are required for Switch platform",
|
||||
path: ["gender", "miiPortraitImage", "miiFeaturesImage", "instructions"],
|
||||
message: "Gender, Mii portrait & features image are required for Switch platform",
|
||||
path: ["gender", "miiPortraitImage", "miiFeaturesImage"],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -161,23 +161,27 @@ export async function POST(request: NextRequest) {
|
|||
const description = uncensoredDescription && profanity.censor(uncensoredDescription);
|
||||
|
||||
// Validate image files
|
||||
let wasImagesModerated = false;
|
||||
const customImages: File[] = [];
|
||||
|
||||
for (const img of [image1, image2, image3]) {
|
||||
if (!img) continue;
|
||||
|
||||
const validation = await validateImage(img);
|
||||
if (!validation.valid) wasImagesModerated = true;
|
||||
if (validation.valid) {
|
||||
customImages.push(img);
|
||||
} else {
|
||||
return rateLimit.sendResponse({ error: `Failed to verify custom image: ${validation.error}` }, validation.status ?? 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Check Mii portrait & features image (Switch)
|
||||
if (platform === "SWITCH") {
|
||||
const portraitValidation = await validateImage(miiPortraitImage);
|
||||
const featuresValidation = await validateImage(miiFeaturesImage);
|
||||
if (!portraitValidation.valid) wasImagesModerated = true;
|
||||
if (!featuresValidation.valid) wasImagesModerated = true;
|
||||
if (!portraitValidation.valid)
|
||||
return rateLimit.sendResponse({ error: `Failed to verify portrait: ${portraitValidation.error}` }, portraitValidation.status ?? 400);
|
||||
if (!featuresValidation.valid)
|
||||
return rateLimit.sendResponse({ error: `Failed to verify features: ${featuresValidation.error}` }, featuresValidation.status ?? 400);
|
||||
}
|
||||
|
||||
const qrBytes = new Uint8Array(qrBytesRaw ?? []);
|
||||
|
|
@ -202,7 +206,7 @@ export async function POST(request: NextRequest) {
|
|||
tags,
|
||||
description,
|
||||
gender: gender ?? "MALE",
|
||||
in_queue: wasImagesModerated || settings.queueEnabled,
|
||||
in_queue: settings.queueEnabled,
|
||||
|
||||
// Automatically detect certain information if on 3DS
|
||||
...(platform === "THREE_DS"
|
||||
|
|
@ -340,5 +344,5 @@ export async function POST(request: NextRequest) {
|
|||
return rateLimit.sendResponse({ error: "Failed to store user images" }, 500);
|
||||
}
|
||||
|
||||
return rateLimit.sendResponse({ success: true, id: miiRecord.id, inQueue: wasImagesModerated });
|
||||
return rateLimit.sendResponse({ success: true, id: miiRecord.id });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
const session = useSession();
|
||||
const [files, setFiles] = useState<FileWithPath[]>([]);
|
||||
|
||||
const handleFilesChange: React.Dispatch<React.SetStateAction<FileWithPath[]>> = (updater) => {
|
||||
hasCustomImagesChanged.current = true;
|
||||
setFiles(updater);
|
||||
};
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(acceptedFiles: FileWithPath[]) => {
|
||||
if (files.length >= 3) return;
|
||||
|
|
@ -439,7 +444,7 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
</Dropzone>
|
||||
</div>
|
||||
|
||||
<ImageList files={files} setFiles={setFiles} />
|
||||
<ImageList files={files} setFiles={handleFilesChange} />
|
||||
|
||||
<hr className="border-zinc-300 my-2" />
|
||||
<div className="flex justify-between items-center">
|
||||
|
|
|
|||
|
|
@ -52,26 +52,6 @@ export async function validateImage(file: File): Promise<{ valid: boolean; error
|
|||
return { valid: false, error: "Image dimensions are invalid. Resolution must be between 128x128 and 8000x8000" };
|
||||
}
|
||||
|
||||
// Check for inappropriate content
|
||||
// https://github.com/trafficlunar/api-moderation
|
||||
try {
|
||||
const blob = new Blob([buffer]);
|
||||
const formData = new FormData();
|
||||
formData.append("image", blob);
|
||||
|
||||
const headers = new Headers();
|
||||
headers.append("token", process.env.TOKEN ?? "");
|
||||
const moderationResponse = await fetch("https://api.trafficlunar.net/moderate/image", { method: "POST", body: formData, headers });
|
||||
const result = await moderationResponse.json();
|
||||
if (result.error) {
|
||||
return { valid: false, error: result.error };
|
||||
}
|
||||
} catch (moderationError) {
|
||||
console.error("Error fetching moderation API:", moderationError);
|
||||
Sentry.captureException(moderationError, { extra: { stage: "moderation-api-fetch" } });
|
||||
return { valid: false, error: "Moderation API is down", status: 503 };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
} catch (error) {
|
||||
console.error("Error validating image:", error);
|
||||
|
|
|
|||
Loading…
Reference in a new issue