From 781682e24ea6e2c068d55f1a2504e159d7ecfb10 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 24 Apr 2026 18:39:45 +0100 Subject: [PATCH 1/6] feat: report reminders --- frontend/src/pages/report/mii.tsx | 5 +++++ frontend/src/pages/report/user.tsx | 4 ++++ frontend/src/pages/submit.tsx | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/report/mii.tsx b/frontend/src/pages/report/mii.tsx index 66a7180..330599d 100644 --- a/frontend/src/pages/report/mii.tsx +++ b/frontend/src/pages/report/mii.tsx @@ -70,6 +70,11 @@ export default function ReportMiiPage() { +

+ REMINDER: Miis without instructions are allowed, as mentioned in the submit form. Be sure to add notes so we + know what's wrong. +

+
+

+ REMINDER: Be sure to add notes so we know what's wrong. +

+
- + From 4275f710b05af0ea0a717ff2ba450491baa10356 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 24 Apr 2026 21:32:53 +0100 Subject: [PATCH 2/6] feat: needs fixing miis --- DEVELOPMENT.md | 56 +++++++++---------- .../migration.sql | 2 + backend/prisma/schema.prisma | 1 + backend/src/app/api/mii/[id]/edit/route.ts | 27 ++++++++- backend/src/app/api/mii/list/route.ts | 1 + backend/src/app/random/page.tsx | 2 +- frontend/src/components/mii/list/index.tsx | 20 +++++-- frontend/src/pages/edit.tsx | 19 +++++++ frontend/src/pages/mii.tsx | 10 ++++ 9 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 backend/prisma/migrations/20260424201540_needs_fixing_reason/migration.sql diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 149521f..30373c0 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -4,36 +4,9 @@ This is probably outdated. Welcome to the TomodachiShare development guide! This project uses [pnpm](https://pnpm.io/) for package management, [Next.js](https://nextjs.org/) with the app router for the backend, [Vite with React](https://vite.dev/) for the frontend, [Prisma](https://prisma.io) for the database, [TailwindCSS](https://tailwindcss.com/) for styling, and [TypeScript](https://www.typescriptlang.org/) for type safety. -## Getting started - -To get the project up and running locally, follow these steps: - -```bash -$ git clone https://github.com/trafficlunar/tomodachi-share -$ cd tomodachi-share -$ pnpm install -``` - -Prisma types are generated automatically, however, if you changed the schema or need to trigger a manual refresh: - -```bash -# Generate Prisma client types -$ pnpm --filter backend prisma generate - -# Or, if you've added new database properties -$ pnpm --filter backend prisma migrate dev -$ pnpm --filter backend prisma generate -``` - -I recommend opting out of Next.js' telemetry program but it is not a requirement. - -```bash -$ pnpm --filter backend exec next telemetry disable -``` - ## Environment variables -You'll need a PostgreSQL database and Redis database. I would recommend using [Docker](https://www.docker.com/) to set these up quickly. Just create a `docker-compose.yaml` with the following content and run `docker compose up -d`: +This step needs to be done before installing packages. You'll need a PostgreSQL database and Redis database. I would recommend using [Docker](https://www.docker.com/) to set these up quickly. Just create a `docker-compose.yaml` with the following content and run `docker compose up -d`: ```yaml services: @@ -85,6 +58,33 @@ For GitHub, navigate to your profile settings, then 'Developer Settings', and cr Google is annoying so I'm not explaining it. +## Getting started + +To get the project up and running locally, follow these steps: + +```bash +$ git clone https://github.com/trafficlunar/tomodachi-share +$ cd tomodachi-share +$ pnpm install +``` + +Prisma types are generated automatically, however, if you changed the schema or need to trigger a manual refresh: + +```bash +# Generate Prisma client types +$ pnpm --filter backend prisma generate + +# Or, if you've added new database properties +$ pnpm --filter backend prisma migrate dev +$ pnpm --filter backend prisma generate +``` + +I recommend opting out of Next.js' telemetry program but it is not a requirement. + +```bash +$ pnpm --filter backend exec next telemetry disable +``` + ## Development Server The frontend and backend need to be ran simulatenously, therefore you need two separate terminals. diff --git a/backend/prisma/migrations/20260424201540_needs_fixing_reason/migration.sql b/backend/prisma/migrations/20260424201540_needs_fixing_reason/migration.sql new file mode 100644 index 0000000..59ee506 --- /dev/null +++ b/backend/prisma/migrations/20260424201540_needs_fixing_reason/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "miis" ADD COLUMN "needsFixing" TEXT; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index a5ccbae..2cda82c 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -77,6 +77,7 @@ model Mii { platform MiiPlatform @default(THREE_DS) quarantined Boolean @default(false) in_queue Boolean @default(false) + needsFixing String? instructions Json? youtubeId String? diff --git a/backend/src/app/api/mii/[id]/edit/route.ts b/backend/src/app/api/mii/[id]/edit/route.ts index d840838..7de330a 100644 --- a/backend/src/app/api/mii/[id]/edit/route.ts +++ b/backend/src/app/api/mii/[id]/edit/route.ts @@ -26,6 +26,11 @@ const editSchema = z.object({ .enum(["true", "false"]) .transform((v) => v === "true") .optional(), + needsFixingReason: z + .string() + .max(256) + .optional() + .transform((val) => (val === "" ? null : val)), gender: z.enum(MiiGender).optional(), makeup: z.enum(MiiMakeup).optional(), miiPortraitImage: z.union([z.instanceof(File), z.any()]).optional(), @@ -86,6 +91,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ tags: rawTags, description: formData.get("description") ?? undefined, quarantined: formData.get("quarantined") ?? undefined, + needsFixingReason: formData.get("needsFixingReason") ?? undefined, gender: formData.get("gender") ?? undefined, makeup: formData.get("makeup") ?? undefined, miiPortraitImage: formData.get("miiPortraitImage"), @@ -103,8 +109,22 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const error = `${path}: ${firstIssue.message}`; return rateLimit.sendResponse({ error }, 400); } - const { name, tags, description, quarantined, gender, makeup, miiPortraitImage, miiFeaturesImage, youtubeId, instructions, image1, image2, image3 } = - parsed.data; + const { + name, + tags, + description, + quarantined, + needsFixingReason, + gender, + makeup, + miiPortraitImage, + miiFeaturesImage, + youtubeId, + instructions, + image1, + image2, + image3, + } = parsed.data; // Validate image files const customImages: File[] = []; @@ -133,7 +153,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ } // Prevent non-admins from quarantining Miis - if (quarantined && session.user?.id?.toString() !== process.env.NEXT_PUBLIC_ADMIN_USER_ID) + if (quarantined && needsFixingReason && session.user?.id?.toString() !== process.env.NEXT_PUBLIC_ADMIN_USER_ID) return rateLimit.sendResponse({ error: `You're not an admin!` }, 401); // Edit Mii in database @@ -142,6 +162,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ if (tags !== undefined) updateData.tags = tags.map((t) => profanity.censor(t)); if (description !== undefined) updateData.description = profanity.censor(description); if (quarantined !== undefined) updateData.quarantined = quarantined; + if (needsFixingReason !== undefined) updateData.needsFixing = needsFixingReason; if (mii.platform === "SWITCH" && gender !== undefined) updateData.gender = gender; if (makeup !== undefined) updateData.makeup = makeup; if (youtubeId !== undefined) updateData.youtubeId = youtubeId; diff --git a/backend/src/app/api/mii/list/route.ts b/backend/src/app/api/mii/list/route.ts index 9b3beda..46db2b0 100644 --- a/backend/src/app/api/mii/list/route.ts +++ b/backend/src/app/api/mii/list/route.ts @@ -99,6 +99,7 @@ export async function GET(request: NextRequest) { allowedCopying: true, quarantined: true, in_queue: true, + needsFixing: true, likeCount: true, // Mii liked check ...(session?.user?.id && { diff --git a/backend/src/app/random/page.tsx b/backend/src/app/random/page.tsx index 36bcee9..d5b9b93 100644 --- a/backend/src/app/random/page.tsx +++ b/backend/src/app/random/page.tsx @@ -9,7 +9,7 @@ export default async function RandomPage() { const randomIndex = Math.floor(Math.random() * count); const randomMii = await prisma.mii.findFirst({ - where: { in_queue: false, quarantined: false }, + where: { in_queue: false, quarantined: false, needsFixing: { not: null } }, skip: randomIndex, take: 1, select: { id: true }, diff --git a/frontend/src/components/mii/list/index.tsx b/frontend/src/components/mii/list/index.tsx index 9f04154..2b146b2 100644 --- a/frontend/src/components/mii/list/index.tsx +++ b/frontend/src/components/mii/list/index.tsx @@ -86,12 +86,20 @@ export default function MiiList({ parentPage, userId, bypassCache }: Props) { key={mii.id} className={`flex flex-col relative bg-zinc-50 rounded-3xl border-2 shadow-lg p-[0.8rem] transition hover:scale-105 hover:bg-cyan-100 hover:border-cyan-600 ${mii.quarantined ? "border-red-300 bg-red-50!" : mii.in_queue ? "border-zinc-400 opacity-70" : "border-zinc-300"}`} > - {mii.in_queue && ( -
- - In Queue -
- )} +
+ {mii.in_queue && ( +
+ + In Queue +
+ )} + {mii.needsFixing && ( +
+ + Needs Fixing +
+ )} +
(defaultInstructions); const [quarantined, setQuarantined] = useState(false); + const [needsFixingReason, setNeedsFixingReason] = useState(""); const hasCustomImagesChanged = useRef(false); const hasMiiPortraitChanged = useRef(false); const hasMiiFeaturesChanged = useRef(false); @@ -80,6 +81,7 @@ export default function EditMiiPage() { if (makeup != mii.makeup) formData.append("makeup", makeup); if (miiPortraitUri) formData.append("miiPortraitUri", miiPortraitUri); if (quarantined != mii.quarantined) formData.append("quarantined", JSON.stringify(quarantined)); + if (needsFixingReason !== mii.needsFixing) formData.append("needsFixingReason", needsFixingReason); if (youtubeId != mii.youtubeId) formData.append("youtubeId", youtubeId); if (minifyInstructions(structuredClone(instructions.current)) !== (mii.instructions as object)) formData.append("instructions", JSON.stringify(instructions.current)); @@ -185,6 +187,7 @@ export default function EditMiiPage() { setMiiFeaturesUri(`${API_URL}/mii/${data.id}/image?type=features`); setYouTubeId(data.youtubeId ?? ""); setQuarantined(data.quarantined); + setNeedsFixingReason(data.needsFixing); instructions.current = deepMerge(defaultInstructions, (data.instructions as object) ?? {}); setLoading(false); }) @@ -297,6 +300,22 @@ export default function EditMiiPage() { setQuarantined(e.target.checked)} /> + +
+ + +
+ setNeedsFixingReason(e.target.value)} + /> +
+
)} diff --git a/frontend/src/pages/mii.tsx b/frontend/src/pages/mii.tsx index a7b5e5b..c3d21e3 100644 --- a/frontend/src/pages/mii.tsx +++ b/frontend/src/pages/mii.tsx @@ -101,6 +101,16 @@ export default function MiiPage() {

)} + {mii.needsFixing && ( +
+ +

+ This Mii won't show up on the main page until fixes are made. +
+ Reason: {mii.needsFixing} +

+
+ )}
{/* Mii Image */} From dbc468acd6e503e827bb003b406a94b3fbae8442 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 24 Apr 2026 21:38:48 +0100 Subject: [PATCH 3/6] fix: show needsFixing miis on own profiles and admin page --- backend/src/app/api/mii/list/route.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/app/api/mii/list/route.ts b/backend/src/app/api/mii/list/route.ts index 46db2b0..279331f 100644 --- a/backend/src/app/api/mii/list/route.ts +++ b/backend/src/app/api/mii/list/route.ts @@ -44,12 +44,13 @@ export async function GET(request: NextRequest) { : userId ? { // Include queued Miis if user is on their profile - ...(Number(session?.user?.id) === userId ? {} : { in_queue: false }), + ...(Number(session?.user?.id) === userId ? {} : { in_queue: false, needsFixing: null }), userId, } : { // Don't show queued Miis on main page in_queue: false, + needsFixing: null, }), // Only show liked miis on likes page ...(parentPage === "likes" && miiIdsLiked && { id: { in: miiIdsLiked } }), From 48d388b1a786dac491ee98f34fea0dcd5dc9b3f8 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 24 Apr 2026 21:43:49 +0100 Subject: [PATCH 4/6] feat: show queued miis on profile --- backend/src/app/api/mii/list/route.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/app/api/mii/list/route.ts b/backend/src/app/api/mii/list/route.ts index 279331f..8f5451b 100644 --- a/backend/src/app/api/mii/list/route.ts +++ b/backend/src/app/api/mii/list/route.ts @@ -43,8 +43,6 @@ export async function GET(request: NextRequest) { ? { in_queue: true } // Only show queued Miis : userId ? { - // Include queued Miis if user is on their profile - ...(Number(session?.user?.id) === userId ? {} : { in_queue: false, needsFixing: null }), userId, } : { From d52d428f1fec558d21c402698daf8db2ce18a892 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 24 Apr 2026 22:14:04 +0100 Subject: [PATCH 5/6] fix: image editor not working in edit form --- backend/next.config.ts | 8 ++++++++ frontend/src/components/submit-form/image-editor.tsx | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/next.config.ts b/backend/next.config.ts index f9ef87e..ff266bf 100644 --- a/backend/next.config.ts +++ b/backend/next.config.ts @@ -13,6 +13,14 @@ const nextConfig: NextConfig = { { key: "Access-Control-Allow-Headers", value: "Content-Type" }, ], }, + { + // for images + source: "/mii/:path*", + headers: [ + { key: "Access-Control-Allow-Origin", value: process.env.NEXT_PUBLIC_FRONTEND_URL || "http://localhost:4321" }, + { key: "Access-Control-Allow-Credentials", value: "true" }, + ], + }, ]; }, }; diff --git a/frontend/src/components/submit-form/image-editor.tsx b/frontend/src/components/submit-form/image-editor.tsx index 758267f..f29c496 100644 --- a/frontend/src/components/submit-form/image-editor.tsx +++ b/frontend/src/components/submit-form/image-editor.tsx @@ -92,7 +92,7 @@ export default function ImageEditorPortrait({ isOpen, setIsOpen, image, setImage
setCrop(c)} className="rounded-2xl border-2 border-amber-500 overflow-hidden max-h-96"> - +
From e8249154d98a268709152a9ba6b4e38620c13099 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Fri, 24 Apr 2026 23:01:21 +0100 Subject: [PATCH 6/6] feat: remove code base change notice --- frontend/src/pages/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index a3f6a22..9617432 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -9,7 +9,6 @@ export default function IndexPage() {

{searchParams.get("tags") ? `Miis tagged with '${searchParams.get("tags")}' - TomodachiShare` : "TomodachiShare - index mii list"}

-

We're currently going through some major code changes therefore some features won't work.

);