diff --git a/backend/next.config.ts b/backend/next.config.ts
index 9253312..f9ef87e 100644
--- a/backend/next.config.ts
+++ b/backend/next.config.ts
@@ -9,7 +9,8 @@ const nextConfig: NextConfig = {
headers: [
{ key: "Access-Control-Allow-Origin", value: process.env.NEXT_PUBLIC_FRONTEND_URL || "http://localhost:4321" },
{ key: "Access-Control-Allow-Credentials", value: "true" },
- { key: "Access-Control-Allow-Methods", value: "GET,POST,PATCH,DELETE,OPTIONS" },
+ { key: "Access-Control-Allow-Methods", value: "GET,POST,DELETE,OPTIONS" },
+ { key: "Access-Control-Allow-Headers", value: "Content-Type" },
],
},
];
diff --git a/backend/src/app/api/admin/accept-mii/route.ts b/backend/src/app/api/admin/accept-mii/route.ts
index 941f9df..06b966b 100644
--- a/backend/src/app/api/admin/accept-mii/route.ts
+++ b/backend/src/app/api/admin/accept-mii/route.ts
@@ -4,7 +4,7 @@ import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { idSchema } from "@tomodachi-share/shared/schemas";
-export async function PATCH(request: NextRequest) {
+export async function POST(request: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/admin/can-submit/route.ts b/backend/src/app/api/admin/can-submit/route.ts
index c5459b3..f164e7d 100644
--- a/backend/src/app/api/admin/can-submit/route.ts
+++ b/backend/src/app/api/admin/can-submit/route.ts
@@ -1,13 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { auth } from "@/lib/auth";
-import { settings } from "@/lib/settings";
+import { settings } from "../../../../lib/settings";
export async function GET() {
return NextResponse.json({ success: true, value: settings.canSubmit });
}
-export async function PATCH(request: NextRequest) {
+export async function POST(request: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/admin/queue/route.ts b/backend/src/app/api/admin/queue/route.ts
index 422fd7d..2f23490 100644
--- a/backend/src/app/api/admin/queue/route.ts
+++ b/backend/src/app/api/admin/queue/route.ts
@@ -1,13 +1,13 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { auth } from "@/lib/auth";
-import { settings } from "@/lib/settings";
+import { settings } from "../../../../lib/settings";
export async function GET() {
return NextResponse.json({ success: true, value: settings.queueEnabled });
}
-export async function PATCH(request: NextRequest) {
+export async function POST(request: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/admin/regenerate-metadata-images/route.ts b/backend/src/app/api/admin/regenerate-metadata-images/route.ts
index 69cb2d3..10237d5 100644
--- a/backend/src/app/api/admin/regenerate-metadata-images/route.ts
+++ b/backend/src/app/api/admin/regenerate-metadata-images/route.ts
@@ -3,7 +3,7 @@ import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { generateMetadataImage } from "@/lib/images";
-export async function PATCH() {
+export async function POST() {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/auth/about-me/route.ts b/backend/src/app/api/auth/about-me/route.ts
index 5269d0c..587daf5 100644
--- a/backend/src/app/api/auth/about-me/route.ts
+++ b/backend/src/app/api/auth/about-me/route.ts
@@ -6,7 +6,7 @@ import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { RateLimit } from "@/lib/rate-limit";
-export async function PATCH(request: NextRequest) {
+export async function POST(request: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/auth/name/route.ts b/backend/src/app/api/auth/name/route.ts
index 2e79541..8a9af28 100644
--- a/backend/src/app/api/auth/name/route.ts
+++ b/backend/src/app/api/auth/name/route.ts
@@ -6,7 +6,7 @@ import { prisma } from "@/lib/prisma";
import { userNameSchema } from "@tomodachi-share/shared/schemas";
import { RateLimit } from "@/lib/rate-limit";
-export async function PATCH(request: NextRequest) {
+export async function POST(request: NextRequest) {
const session = await auth();
if (!session || !session.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/auth/picture/route.ts b/backend/src/app/api/auth/picture/route.ts
index 7f5d5fa..c5f5775 100644
--- a/backend/src/app/api/auth/picture/route.ts
+++ b/backend/src/app/api/auth/picture/route.ts
@@ -17,7 +17,7 @@ const formDataSchema = z.object({
image: z.union([z.instanceof(File), z.any()]).optional(),
});
-export async function PATCH(request: NextRequest) {
+export async function POST(request: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/mii/[id]/edit/route.ts b/backend/src/app/api/mii/[id]/edit/route.ts
index 930bc8e..d840838 100644
--- a/backend/src/app/api/mii/[id]/edit/route.ts
+++ b/backend/src/app/api/mii/[id]/edit/route.ts
@@ -14,7 +14,7 @@ import { idSchema, nameSchema, switchMiiInstructionsSchema, tagsSchema } from "@
import { generateMetadataImage, validateImage } from "@/lib/images";
import { RateLimit } from "@/lib/rate-limit";
import { minifyInstructions, SwitchMiiInstructions } from "@tomodachi-share/shared";
-import { settings } from "@/lib/settings";
+import { settings } from "../../../../../lib/settings";
const uploadsDirectory = path.join(process.cwd(), "uploads", "mii");
@@ -41,7 +41,7 @@ const editSchema = z.object({
image3: z.union([z.instanceof(File), z.any()]).optional(),
});
-export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
+export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/mii/[id]/like/route.ts b/backend/src/app/api/mii/[id]/like/route.ts
index 9464bad..332f5bb 100644
--- a/backend/src/app/api/mii/[id]/like/route.ts
+++ b/backend/src/app/api/mii/[id]/like/route.ts
@@ -5,7 +5,7 @@ import { prisma } from "@/lib/prisma";
import { idSchema } from "@tomodachi-share/shared/schemas";
import { RateLimit } from "@/lib/rate-limit";
-export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
+export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
diff --git a/backend/src/app/api/submit/route.ts b/backend/src/app/api/submit/route.ts
index c062842..178d776 100644
--- a/backend/src/app/api/submit/route.ts
+++ b/backend/src/app/api/submit/route.ts
@@ -18,7 +18,7 @@ import Mii from "../../../../../shared/src/mii.js/mii";
import { convertQrCode, minifyInstructions, ThreeDsTomodachiLifeMii } from "@tomodachi-share/shared";
import { SwitchMiiInstructions } from "@tomodachi-share/shared";
-import { settings } from "@/lib/settings";
+import { settings } from "../../../lib/settings";
const uploadsDirectory = path.join(process.cwd(), "uploads", "mii");
diff --git a/backend/src/app/out/page.tsx b/backend/src/app/out/page.tsx
deleted file mode 100644
index d72c91b..0000000
--- a/backend/src/app/out/page.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Metadata } from "next";
-import Link from "next/link";
-import { redirect } from "next/navigation";
-import { Icon } from "@iconify/react";
-
-export const metadata: Metadata = {
- title: "Leaving TomodachiShare",
- description: "Warning: You are leaving TomodachiShare, proceed with caution",
-};
-
-interface Props {
- searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
-}
-
-export default async function LinkOutPage({ searchParams }: Props) {
- const url = (await searchParams).url;
- if (!url || Array.isArray(url)) redirect("/");
-
- let parsed: URL;
- try {
- parsed = new URL(url);
- } catch {
- redirect("/"); // redirect if URL is invalid
- }
-
- // Next.js doesn't allow attacks like these but you can never be too safe
- if (!["http:", "https:"].includes(parsed.protocol)) redirect("/");
-
- const isSafe = Array.from(SAFE_LINKS).some((domain) => parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`));
- if (isSafe) redirect(url);
-
- return (
-
-
-
-
- Warning
-
-
You're attempting to leave TomodachiShare island! The destination website is potentially dangerous.
-
-
- {url}
-
-
-
-
-
- Travel Back
-
-
-
- Continue
-
-
-
-
- );
-}
-
-const SAFE_LINKS = new Set([
- "tomodachishare.com",
- "trafficlunar.net",
- "youtube.com",
- "youtu.be",
- "twitter.com",
- "x.com",
- "reddit.com",
- "tiktok.com",
- "tumblr.com",
- "instagram.com",
- "wikipedia.org",
-]);
diff --git a/backend/src/lib/auth.ts b/backend/src/lib/auth.ts
index 9a04006..71f2774 100644
--- a/backend/src/lib/auth.ts
+++ b/backend/src/lib/auth.ts
@@ -18,6 +18,7 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
sameSite: "none",
path: "/",
secure: true,
+ domain: process.env.NODE_ENV === "production" ? ".tomodachishare.com" : "localhost",
},
},
},
diff --git a/frontend/src/components/admin/control-center.tsx b/frontend/src/components/admin/control-center.tsx
index 4616a07..320690d 100644
--- a/frontend/src/components/admin/control-center.tsx
+++ b/frontend/src/components/admin/control-center.tsx
@@ -1,45 +1,45 @@
-// import { settings } from "@/lib/settings";
-// import { useState } from "react";
-
-// export default function ControlCenter() {
-// const [canSubmit, setCanSubmit] = useState(settings.canSubmit);
-// const [isQueueEnabled, setIsQeueueEnabled] = useState(settings.queueEnabled);
-
-// const onClickSet = async () => {
-// await fetch("/api/admin/can-submit", { method: "PATCH", body: JSON.stringify(canSubmit) });
-// await fetch("/api/admin/queue", { method: "PATCH", body: JSON.stringify(isQueueEnabled) });
-// };
-
-// return (
-//
-//
-// setCanSubmit(e.target.checked)}
-// />
-//
-//
-//
-// setIsQeueueEnabled(e.target.checked)}
-// />
-//
-//
-
-//
-//
-//
-//
-// );
-// }
+// import { settings } from "@/lib/settings";
+// import { useState } from "react";
+
+// export default function ControlCenter() {
+// const [canSubmit, setCanSubmit] = useState(settings.canSubmit);
+// const [isQueueEnabled, setIsQeueueEnabled] = useState(settings.queueEnabled);
+
+// const onClickSet = async () => {
+// await fetch("/api/admin/can-submit", { method: "POST", body: JSON.stringify(canSubmit) });
+// await fetch("/api/admin/queue", { method: "POST", body: JSON.stringify(isQueueEnabled) });
+// };
+
+// return (
+//
+//
+// setCanSubmit(e.target.checked)}
+// />
+//
+//
+//
+// setIsQeueueEnabled(e.target.checked)}
+// />
+//
+//
+
+//
+//
+//
+//
+// );
+// }
diff --git a/frontend/src/components/admin/regenerate-images.tsx b/frontend/src/components/admin/regenerate-images.tsx
index fda6a53..8e15372 100644
--- a/frontend/src/components/admin/regenerate-images.tsx
+++ b/frontend/src/components/admin/regenerate-images.tsx
@@ -1,84 +1,84 @@
-import { useEffect, useState } from "react";
-import { createPortal } from "react-dom";
-
-import { Icon } from "@iconify/react";
-import SubmitButton from "../submit-button";
-
-export default function RegenerateImagesButton() {
- const [isOpen, setIsOpen] = useState(false);
- const [isVisible, setIsVisible] = useState(false);
-
- const [error, setError] = useState(undefined);
-
- const handleSubmit = async () => {
- const response = await fetch("/api/admin/regenerate-metadata-images", { method: "PATCH" });
-
- if (!response.ok) {
- const data = await response.json();
- setError(data.error);
-
- return;
- }
-
- close();
- };
-
- const close = () => {
- setIsVisible(false);
- setTimeout(() => {
- setIsOpen(false);
- }, 300);
- };
-
- useEffect(() => {
- if (isOpen) {
- // slight delay to trigger animation
- setTimeout(() => setIsVisible(true), 10);
- }
- }, [isOpen]);
-
- return (
- <>
-
-
- {isOpen &&
- createPortal(
-
-
-
-
-
-
Regenerate Images
-
-
-
-
Are you sure? This will delete and regenerate every metadata image.
-
- {error &&
Error: {error}}
-
-
-
-
-
-
-
,
- document.body,
- )}
- >
- );
-}
+import { useEffect, useState } from "react";
+import { createPortal } from "react-dom";
+
+import { Icon } from "@iconify/react";
+import SubmitButton from "../submit-button";
+
+export default function RegenerateImagesButton() {
+ const [isOpen, setIsOpen] = useState(false);
+ const [isVisible, setIsVisible] = useState(false);
+
+ const [error, setError] = useState(undefined);
+
+ const handleSubmit = async () => {
+ const response = await fetch("/api/admin/regenerate-metadata-images", { method: "POST" });
+
+ if (!response.ok) {
+ const data = await response.json();
+ setError(data.error);
+
+ return;
+ }
+
+ close();
+ };
+
+ const close = () => {
+ setIsVisible(false);
+ setTimeout(() => {
+ setIsOpen(false);
+ }, 300);
+ };
+
+ useEffect(() => {
+ if (isOpen) {
+ // slight delay to trigger animation
+ setTimeout(() => setIsVisible(true), 10);
+ }
+ }, [isOpen]);
+
+ return (
+ <>
+
+
+ {isOpen &&
+ createPortal(
+
+
+
+
+
+
Regenerate Images
+
+
+
+
Are you sure? This will delete and regenerate every metadata image.
+
+ {error &&
Error: {error}}
+
+
+
+
+
+
+
,
+ document.body,
+ )}
+ >
+ );
+}
diff --git a/frontend/src/components/header-profile.tsx b/frontend/src/components/header-profile.tsx
deleted file mode 100644
index 4853946..0000000
--- a/frontend/src/components/header-profile.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { Icon } from "@iconify/react";
-import { useEffect } from "react";
-import { useStore } from "@nanostores/react";
-import { session } from "../session";
-import { Link } from "react-router";
-
-export default function HeaderProfile() {
- const API_BASE_URL = import.meta.env.VITE_API_URL;
- const $session = useStore(session);
-
- useEffect(() => {
- fetch(`${API_BASE_URL}/api/auth/session`, { credentials: "include" })
- .then((res) => {
- if (!res.ok) throw new Error("Failed to get session");
- return res.json();
- })
- .then((data) => {
- session.set(data);
- })
- .catch((err) => {
- console.error(err);
- });
- }, []);
-
- return (
- <>
- {!$session?.user ? (
-
-
- Login
-
-
- ) : (
- <>
-
-
-
- {$session?.user?.name ?? "unknown"}
-
-
-
-
-
-
-
- >
- )}
- >
- );
-}
diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx
index 0c515bf..c3d8493 100644
--- a/frontend/src/components/header.tsx
+++ b/frontend/src/components/header.tsx
@@ -1,9 +1,27 @@
import { Icon } from "@iconify/react";
import SearchBar from "./search-bar";
-import HeaderProfile from "./header-profile";
import { Link } from "react-router";
+import { useStore } from "@nanostores/react";
+import { session } from "../session";
+import { useEffect } from "react";
export default function Header() {
+ const $session = useStore(session);
+
+ useEffect(() => {
+ fetch(`${import.meta.env.VITE_API_URL}/api/auth/session`, { credentials: "include" })
+ .then((res) => {
+ if (!res.ok) throw new Error("Failed to get session");
+ return res.json();
+ })
+ .then((data) => {
+ session.set(data);
+ })
+ .catch((err) => {
+ console.error(err);
+ });
+ }, []);
+
return (
-
+ {!$session?.user ? (
+
+
+ Login
+
+
+ ) : (
+ <>
+
+
+
{
+ e.currentTarget.onerror = null; // Prevent infinite loops
+ e.currentTarget.src = "/guest.png";
+ }}
+ alt="profile picture"
+ width={40}
+ height={40}
+ className="rounded-full aspect-square object-cover h-full bg-white outline-2 outline-orange-400"
+ />
+ {$session?.user?.name ?? "unknown"}
+
+
+
+
+
+
+
+ >
+ )}
);
diff --git a/frontend/src/components/like-button.tsx b/frontend/src/components/like-button.tsx
index 9082918..e764f24 100644
--- a/frontend/src/components/like-button.tsx
+++ b/frontend/src/components/like-button.tsx
@@ -29,7 +29,7 @@ export default function LikeButton({ likes, isLiked, disabled, abbreviate, big }
// setIsAnimating(true);
// setTimeout(() => setIsAnimating(false), 1000); // match animation duration
// }
- // const response = await fetch(`/api/mii/${miiId}/like`, { method: "PATCH" });
+ // const response = await fetch(`/api/mii/${miiId}/like`, { method: "POST" });
// if (response.ok) {
// const { liked, count } = await response.json();
// setIsLikedState(liked);
diff --git a/frontend/src/components/mii/list/index.tsx b/frontend/src/components/mii/list/index.tsx
index 5cfaa97..4b8cb9d 100644
--- a/frontend/src/components/mii/list/index.tsx
+++ b/frontend/src/components/mii/list/index.tsx
@@ -80,21 +80,27 @@ export default function MiiList({ parentPage, userId }: Props) {
)}
- {parentPage !== "admin" ?
-
+
+
+ ) : (
+ `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=image${index}`),
+ ]}
/>
- : `${import.meta.env.VITE_API_URL}/mii/${mii.id}/image?type=image${index}`),
- ]}
- />}
+ )}
@@ -141,7 +147,7 @@ export default function MiiList({ parentPage, userId }: Props) {