diff --git a/prisma/migrations/20260324215914_remove_usernames/migration.sql b/prisma/migrations/20260324215914_remove_usernames/migration.sql new file mode 100644 index 0000000..8a1de47 --- /dev/null +++ b/prisma/migrations/20260324215914_remove_usernames/migration.sql @@ -0,0 +1,16 @@ +/* + Warnings: + + - You are about to drop the column `username` on the `users` table. All the data in the column will be lost. + - You are about to drop the column `usernameUpdatedAt` on the `users` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "users_username_key"; + +-- AlterTable +ALTER TABLE "miis" ALTER COLUMN "allowedCopying" DROP NOT NULL; + +-- AlterTable +ALTER TABLE "users" DROP COLUMN "username", +DROP COLUMN "usernameUpdatedAt"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7c029bf..6a0b162 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,7 +9,6 @@ datasource db { model User { id Int @id @default(autoincrement()) - username String? @unique name String email String @unique emailVerified DateTime? @@ -19,8 +18,7 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - usernameUpdatedAt DateTime? - imageUpdatedAt DateTime? + imageUpdatedAt DateTime? accounts Account[] sessions Session[] diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 66af4c6..984cf38 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -21,7 +21,7 @@ export const metadata: Metadata = { export default async function AdminPage() { const session = await auth(); - if (!session || Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) redirect("/404"); + if (!session || Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) redirect("/404"); return (
diff --git a/src/app/api/admin/banner/route.ts b/src/app/api/admin/banner/route.ts index 179b812..f7ad355 100644 --- a/src/app/api/admin/banner/route.ts +++ b/src/app/api/admin/banner/route.ts @@ -11,7 +11,7 @@ export async function POST(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); const body = await request.text(); bannerText = body; @@ -23,7 +23,7 @@ export async function DELETE() { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); bannerText = null; return NextResponse.json({ success: true }); diff --git a/src/app/api/admin/can-submit/route.ts b/src/app/api/admin/can-submit/route.ts index 6623dc7..330a5d9 100644 --- a/src/app/api/admin/can-submit/route.ts +++ b/src/app/api/admin/can-submit/route.ts @@ -12,7 +12,7 @@ export async function PATCH(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); const body = await request.json(); const validatedCanSubmit = z.boolean().safeParse(body); diff --git a/src/app/api/admin/lookup/route.ts b/src/app/api/admin/lookup/route.ts index 6f32ed0..350cedb 100644 --- a/src/app/api/admin/lookup/route.ts +++ b/src/app/api/admin/lookup/route.ts @@ -8,7 +8,7 @@ export async function GET(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); const searchParams = request.nextUrl.searchParams; const parsed = idSchema.safeParse(searchParams.get("id")); @@ -51,7 +51,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ success: true, name: user.name, - username: user.username, image: user.image, createdAt: user.createdAt, punishments: user.punishments, diff --git a/src/app/api/admin/punish/route.ts b/src/app/api/admin/punish/route.ts index b12b52d..c341973 100644 --- a/src/app/api/admin/punish/route.ts +++ b/src/app/api/admin/punish/route.ts @@ -30,7 +30,7 @@ export async function POST(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); const searchParams = request.nextUrl.searchParams; const parsedUserId = idSchema.safeParse(searchParams.get("id")); @@ -69,7 +69,7 @@ export async function DELETE(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); const searchParams = request.nextUrl.searchParams; const parsedPunishmentId = idSchema.safeParse(searchParams.get("id")); diff --git a/src/app/api/admin/regenerate-metadata-images/route.ts b/src/app/api/admin/regenerate-metadata-images/route.ts index 86209f0..69cb2d3 100644 --- a/src/app/api/admin/regenerate-metadata-images/route.ts +++ b/src/app/api/admin/regenerate-metadata-images/route.ts @@ -7,7 +7,7 @@ export async function PATCH() { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - if (Number(session.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + if (Number(session.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) return NextResponse.json({ error: "Forbidden" }, { status: 403 }); // Start processing in background regenerateImages().catch(console.error); diff --git a/src/app/api/auth/about-me/route.ts b/src/app/api/auth/about-me/route.ts index 004d418..c295fba 100644 --- a/src/app/api/auth/about-me/route.ts +++ b/src/app/api/auth/about-me/route.ts @@ -10,7 +10,7 @@ import { RateLimit } from "@/lib/rate-limit"; export async function PATCH(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user?.id, name: session.user?.name }); const rateLimit = new RateLimit(request, 3); const check = await rateLimit.handle(); @@ -24,7 +24,7 @@ export async function PATCH(request: NextRequest) { try { await prisma.user.update({ - where: { id: Number(session.user.id) }, + where: { id: Number(session.user?.id) }, data: { description: profanity.censor(description) }, }); } catch (error) { diff --git a/src/app/api/auth/delete/route.ts b/src/app/api/auth/delete/route.ts index d9d03c0..f6a7b53 100644 --- a/src/app/api/auth/delete/route.ts +++ b/src/app/api/auth/delete/route.ts @@ -8,7 +8,7 @@ import { RateLimit } from "@/lib/rate-limit"; export async function DELETE(request: NextRequest) { const session = await auth(); if (!session || !session.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user.id, name: session.user.name }); const rateLimit = new RateLimit(request, 1); const check = await rateLimit.handle(); diff --git a/src/app/api/auth/display-name/route.ts b/src/app/api/auth/display-name/route.ts deleted file mode 100644 index 82df651..0000000 --- a/src/app/api/auth/display-name/route.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import * as Sentry from "@sentry/nextjs"; -import { profanity } from "@2toad/profanity"; - -import { auth } from "@/lib/auth"; -import { prisma } from "@/lib/prisma"; -import { displayNameSchema } from "@/lib/schemas"; -import { RateLimit } from "@/lib/rate-limit"; - -export async function PATCH(request: NextRequest) { - const session = await auth(); - if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); - - const rateLimit = new RateLimit(request, 3); - const check = await rateLimit.handle(); - if (check) return check; - - const { displayName } = await request.json(); - if (!displayName) return rateLimit.sendResponse({ error: "New display name is required" }, 400); - - const validation = displayNameSchema.safeParse(displayName); - if (!validation.success) return rateLimit.sendResponse({ error: validation.error.issues[0].message }, 400); - - // Check for inappropriate words - if (profanity.exists(displayName)) return rateLimit.sendResponse({ error: "Display name contains inappropriate words" }, 400); - - try { - await prisma.user.update({ - where: { id: Number(session.user.id) }, - data: { name: displayName }, - }); - } catch (error) { - console.error("Failed to update display name:", error); - Sentry.captureException(error, { extra: { stage: "update-display-name" } }); - return rateLimit.sendResponse({ error: "Failed to update display name" }, 500); - } - - return rateLimit.sendResponse({ success: true }); -} diff --git a/src/app/api/auth/name/route.ts b/src/app/api/auth/name/route.ts new file mode 100644 index 0000000..e75e9db --- /dev/null +++ b/src/app/api/auth/name/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from "next/server"; +import * as Sentry from "@sentry/nextjs"; +import { profanity } from "@2toad/profanity"; + +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { userNameSchema } from "@/lib/schemas"; +import { RateLimit } from "@/lib/rate-limit"; + +export async function PATCH(request: NextRequest) { + const session = await auth(); + if (!session || !session.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + Sentry.setUser({ id: session.user.id, name: session.user.name }); + + const rateLimit = new RateLimit(request, 3); + const check = await rateLimit.handle(); + if (check) return check; + + const { name } = await request.json(); + if (!name) return rateLimit.sendResponse({ error: "New name is required" }, 400); + + const validation = userNameSchema.safeParse(name); + if (!validation.success) return rateLimit.sendResponse({ error: validation.error.issues[0].message }, 400); + + // Check for inappropriate words + if (profanity.exists(name)) return rateLimit.sendResponse({ error: "Name contains inappropriate words" }, 400); + + try { + await prisma.user.update({ + where: { id: Number(session.user.id) }, + data: { name }, + }); + } catch (error) { + console.error("Failed to update name:", error); + Sentry.captureException(error, { extra: { stage: "update-name" } }); + return rateLimit.sendResponse({ error: "Failed to update name" }, 500); + } + + return rateLimit.sendResponse({ success: true }); +} diff --git a/src/app/api/auth/picture/route.ts b/src/app/api/auth/picture/route.ts index 813f7f8..dcae3b4 100644 --- a/src/app/api/auth/picture/route.ts +++ b/src/app/api/auth/picture/route.ts @@ -21,14 +21,14 @@ const formDataSchema = z.object({ export async function PATCH(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user?.id, name: session.user?.name }); const rateLimit = new RateLimit(request, 3); const check = await rateLimit.handle(); if (check) return check; // Check if profile picture was updated in the last 7 days - const user = await prisma.user.findUnique({ where: { id: Number(session.user.id) } }); + const user = await prisma.user.findUnique({ where: { id: Number(session.user?.id) } }); if (user && user.imageUpdatedAt) { const timePeriod = dayjs().subtract(7, "days"); const lastUpdate = dayjs(user.imageUpdatedAt); @@ -48,7 +48,7 @@ export async function PATCH(request: NextRequest) { // If there is no image, set the profile picture to the guest image if (!image) { await prisma.user.update({ - where: { id: Number(session.user.id) }, + where: { id: Number(session.user?.id) }, data: { image: `/guest.png`, imageUpdatedAt: new Date() }, }); @@ -65,7 +65,7 @@ export async function PATCH(request: NextRequest) { try { const buffer = Buffer.from(await image.arrayBuffer()); const pngBuffer = await sharp(buffer, { animated: true }).resize({ width: 128, height: 128 }).png({ quality: 85 }).toBuffer(); - const fileLocation = path.join(uploadsDirectory, `${session.user.id}.png`); + const fileLocation = path.join(uploadsDirectory, `${session.user?.id}.png`); await fs.writeFile(fileLocation, pngBuffer); } catch (error) { @@ -76,8 +76,8 @@ export async function PATCH(request: NextRequest) { try { await prisma.user.update({ - where: { id: Number(session.user.id) }, - data: { image: `/profile/${session.user.id}/picture`, imageUpdatedAt: new Date() }, + where: { id: Number(session.user?.id) }, + data: { image: `/profile/${session.user?.id}/picture`, imageUpdatedAt: new Date() }, }); } catch (error) { console.error("Failed to update profile picture:", error); diff --git a/src/app/api/auth/username/route.ts b/src/app/api/auth/username/route.ts deleted file mode 100644 index de5b6b9..0000000 --- a/src/app/api/auth/username/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import * as Sentry from "@sentry/nextjs"; - -import dayjs from "dayjs"; -import { profanity } from "@2toad/profanity"; - -import { auth } from "@/lib/auth"; -import { prisma } from "@/lib/prisma"; -import { usernameSchema } from "@/lib/schemas"; -import { RateLimit } from "@/lib/rate-limit"; - -export async function PATCH(request: NextRequest) { - const session = await auth(); - if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); - - const rateLimit = new RateLimit(request, 3); - const check = await rateLimit.handle(); - if (check) return check; - - const { username } = await request.json(); - if (!username) return rateLimit.sendResponse({ error: "New username is required" }, 400); - - // Check if username was updated in the last 90 days - const user = await prisma.user.findUnique({ where: { id: Number(session.user.id) } }); - if (user && user.usernameUpdatedAt) { - const timePeriod = dayjs().subtract(90, "days"); - const lastUpdate = dayjs(user.usernameUpdatedAt); - - if (lastUpdate.isAfter(timePeriod)) return rateLimit.sendResponse({ error: "Username was changed in the last 90 days" }, 400); - } - - const validation = usernameSchema.safeParse(username); - if (!validation.success) return rateLimit.sendResponse({ error: validation.error.issues[0].message }, 400); - - // Check for inappropriate words - if (profanity.exists(username)) return rateLimit.sendResponse({ error: "Username contains inappropriate words" }, 400); - - const existingUser = await prisma.user.findUnique({ where: { username } }); - if (existingUser) return rateLimit.sendResponse({ error: "Username is already taken" }, 400); - - try { - await prisma.user.update({ - where: { id: Number(session.user.id) }, - data: { username, usernameUpdatedAt: new Date() }, - }); - } catch (error) { - console.error("Failed to update username:", error); - Sentry.captureException(error, { extra: { stage: "update-username" } }); - return rateLimit.sendResponse({ error: "Failed to update username" }, 500); - } - - return rateLimit.sendResponse({ success: true }); -} diff --git a/src/app/api/mii/[id]/delete/route.ts b/src/app/api/mii/[id]/delete/route.ts index f99e45c..d6d3e59 100644 --- a/src/app/api/mii/[id]/delete/route.ts +++ b/src/app/api/mii/[id]/delete/route.ts @@ -14,7 +14,7 @@ const uploadsDirectory = path.join(process.cwd(), "uploads", "mii"); export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user?.id, name: session.user?.name }); const rateLimit = new RateLimit(request, 30, "/api/mii/delete"); const check = await rateLimit.handle(); @@ -33,7 +33,7 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise }); if (!mii) return rateLimit.sendResponse({ error: "Mii not found" }, 404); - if (!(Number(session.user.id) === mii.userId || Number(session.user.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID))) + if (!(Number(session.user?.id) === mii.userId || Number(session.user?.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID))) return rateLimit.sendResponse({ error: "You don't have ownership of that Mii" }, 403); const miiUploadsDirectory = path.join(uploadsDirectory, miiId.toString()); diff --git a/src/app/api/mii/[id]/edit/route.ts b/src/app/api/mii/[id]/edit/route.ts index 29a35f1..20e053c 100644 --- a/src/app/api/mii/[id]/edit/route.ts +++ b/src/app/api/mii/[id]/edit/route.ts @@ -29,7 +29,7 @@ const editSchema = z.object({ export async function PATCH(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user?.id, name: session.user?.name }); const rateLimit = new RateLimit(request, 1); // no grouped pathname; edit each mii 1 time a minute const check = await rateLimit.handle(); @@ -49,7 +49,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< }); if (!mii) return rateLimit.sendResponse({ error: "Mii not found" }, 404); - if (!(Number(session.user.id) === mii.userId || Number(session.user.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID))) + if (!(Number(session.user?.id) === mii.userId || Number(session.user?.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID))) return rateLimit.sendResponse({ error: "You don't have ownership of that Mii" }, 403); // Parse form data diff --git a/src/app/api/mii/[id]/like/route.ts b/src/app/api/mii/[id]/like/route.ts index 3cba66c..3a1e213 100644 --- a/src/app/api/mii/[id]/like/route.ts +++ b/src/app/api/mii/[id]/like/route.ts @@ -22,7 +22,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< const existingLike = await tx.like.findUnique({ where: { userId_miiId: { - userId: Number(session.user.id), + userId: Number(session.user?.id), miiId, }, }, @@ -33,7 +33,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< await tx.like.delete({ where: { userId_miiId: { - userId: Number(session.user.id), + userId: Number(session.user?.id), miiId, }, }, @@ -42,7 +42,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< // Add a like if it doesn't exist await tx.like.create({ data: { - userId: Number(session.user.id), + userId: Number(session.user?.id), miiId, }, }); diff --git a/src/app/api/report/route.ts b/src/app/api/report/route.ts index ed8684d..3897082 100644 --- a/src/app/api/report/route.ts +++ b/src/app/api/report/route.ts @@ -19,7 +19,7 @@ const reportSchema = z.object({ export async function POST(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user?.id, name: session.user?.name }); const rateLimit = new RateLimit(request, 2); const check = await rateLimit.handle(); @@ -35,7 +35,7 @@ export async function POST(request: NextRequest) { include: { user: { select: { - username: true; + name: true; }; }; }; @@ -48,7 +48,7 @@ export async function POST(request: NextRequest) { include: { user: { select: { - username: true, + name: true, }, }, }, @@ -66,7 +66,7 @@ export async function POST(request: NextRequest) { where: { targetId: id, reportType: type.toUpperCase() as ReportType, - authorId: Number(session.user.id), + authorId: Number(session.user?.id), }, }); @@ -79,7 +79,7 @@ export async function POST(request: NextRequest) { targetId: id, reason: reason.toUpperCase() as ReportReason, reasonNotes: notes, - authorId: Number(session.user.id), + authorId: Number(session.user?.id), creatorId: mii ? mii.userId : undefined, }, }); @@ -92,11 +92,11 @@ export async function POST(request: NextRequest) { // Send notification to ntfy if (process.env.NTFY_URL) { // This is only shown if report type is MII - const miiCreatorMessage = mii ? `by @${mii.user.username} (ID: ${mii.userId})` : ""; + const miiCreatorMessage = mii ? `by ${mii.user.name} (ID: ${mii.userId})` : ""; await fetch(process.env.NTFY_URL, { method: "POST", - body: `Report by @${session.user.username} (ID: ${session.user.id}) on ${type.toUpperCase()} (ID: ${id}) ${miiCreatorMessage}`, + body: `Report by ${session.user?.name} (ID: ${session.user?.id}) on ${type.toUpperCase()} (ID: ${id}) ${miiCreatorMessage}`, headers: { Title: "Report recieved - TomodachiShare", Priority: "urgent", diff --git a/src/app/api/return/route.ts b/src/app/api/return/route.ts index c3cb3eb..6da81b6 100644 --- a/src/app/api/return/route.ts +++ b/src/app/api/return/route.ts @@ -14,7 +14,7 @@ export async function DELETE(request: NextRequest) { const activePunishment = await prisma.punishment.findFirst({ where: { - userId: Number(session.user.id), + userId: Number(session.user?.id), returned: false, }, include: { diff --git a/src/app/api/submit/route.ts b/src/app/api/submit/route.ts index 11cd0fa..5236323 100644 --- a/src/app/api/submit/route.ts +++ b/src/app/api/submit/route.ts @@ -37,7 +37,7 @@ const submitSchema = z.object({ export async function POST(request: NextRequest) { const session = await auth(); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - Sentry.setUser({ id: session.user.id, username: session.user.username }); + Sentry.setUser({ id: session.user?.id, name: session.user?.name }); const rateLimit = new RateLimit(request, 2); const check = await rateLimit.handle(); @@ -108,7 +108,7 @@ export async function POST(request: NextRequest) { // Create Mii in database const miiRecord = await prisma.mii.create({ data: { - userId: Number(session.user.id), + userId: Number(session.user?.id), name, tags, description, @@ -169,7 +169,7 @@ export async function POST(request: NextRequest) { const codeFileLocation = path.join(miiUploadsDirectory, "qr-code.png"); await fs.writeFile(codeFileLocation, codePngBuffer); - await generateMetadataImage(miiRecord, session.user.name!); + await generateMetadataImage(miiRecord, session.user?.name!); } catch (error) { // Clean up if something went wrong await prisma.mii.delete({ where: { id: miiRecord.id } }); diff --git a/src/app/create-username/page.tsx b/src/app/create-username/page.tsx deleted file mode 100644 index 3554e71..0000000 --- a/src/app/create-username/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Metadata } from "next"; -import { redirect } from "next/navigation"; -import { auth } from "@/lib/auth"; -import UsernameForm from "@/components/username-form"; - -export const metadata: Metadata = { - title: "Create your Username - TomodachiShare", - description: "Pick a unique username to start using TomodachiShare", - robots: { - index: false, - follow: false, - }, -}; - -export default async function CreateUsernamePage() { - const session = await auth(); - - // If the user is not logged in or already has a username, redirect - if (!session || session?.user.username) { - redirect("/"); - } - - return ( -
-
-

Welcome to the island!

- -
-
- Please create a username -
-
- - -
-
- ); -} diff --git a/src/app/edit/[id]/page.tsx b/src/app/edit/[id]/page.tsx index 162b825..3008146 100644 --- a/src/app/edit/[id]/page.tsx +++ b/src/app/edit/[id]/page.tsx @@ -44,7 +44,7 @@ export default async function MiiPage({ params }: Props) { }); // Check ownership - if (!mii || (Number(session?.user.id) !== mii.userId && Number(session?.user.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID))) redirect("/404"); + if (!mii || (Number(session?.user?.id) !== mii.userId && Number(session?.user?.id) !== Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID))) redirect("/404"); return ; } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index e848d7e..ac02e15 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -12,9 +12,7 @@ export default async function LoginPage() { const session = await auth(); // If the user is already logged in, redirect - if (session) { - redirect("/"); - } + if (session) redirect("/"); return (
diff --git a/src/app/mii/[id]/page.tsx b/src/app/mii/[id]/page.tsx index 16a574e..5f5c107 100644 --- a/src/app/mii/[id]/page.tsx +++ b/src/app/mii/[id]/page.tsx @@ -30,7 +30,7 @@ export async function generateMetadata({ params }: Props): Promise { include: { user: { select: { - username: true, + name: true, }, }, _count: { @@ -44,28 +44,28 @@ export async function generateMetadata({ params }: Props): Promise { const metadataImageUrl = `/mii/${mii.id}/image?type=metadata`; - const username = `@${mii.user.username}`; + const name = `@${mii.user.name}`; return { metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL!), title: `${mii.name} - TomodachiShare`, - description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${username} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`, + description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${name} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`, keywords: ["mii", "tomodachi life", "nintendo", "tomodachishare", "tomodachi-share", "mii creator", "mii collection", ...mii.tags], - creator: username, + creator: name, openGraph: { type: "article", title: `${mii.name} - TomodachiShare`, - description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${username} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`, + description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${name} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`, images: [{ url: metadataImageUrl, alt: `${mii.name}, ${mii.tags.join(", ")} ${mii.gender} Mii character` }], publishedTime: mii.createdAt.toISOString(), - authors: username, + authors: name, }, twitter: { card: "summary_large_image", title: `${mii.name} - TomodachiShare`, - description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${username} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`, + description: `Check out '${mii.name}', a Tomodachi Life Mii created by ${name} on TomodachiShare. From ${mii.islandName} Island with ${mii._count.likedBy} likes.`, images: [{ url: metadataImageUrl, alt: `${mii.name}, ${mii.tags.join(", ")} ${mii.gender} Mii character` }], - creator: username, + creator: name, }, alternates: { canonical: `/mii/${mii.id}`, @@ -85,7 +85,6 @@ export default async function MiiPage({ params }: Props) { user: { select: { name: true, - username: true, }, }, likedBy: session?.user @@ -215,7 +214,7 @@ export default async function MiiPage({ params }: Props) { {/* Buttons */}
- {session && (Number(session.user.id) === mii.userId || Number(session.user.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) && ( + {session && (Number(session.user?.id) === mii.userId || Number(session.user?.id) === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID)) && ( <> diff --git a/src/app/page.tsx b/src/app/page.tsx index 801e5e7..8204c7f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -39,9 +39,6 @@ export default async function Page({ searchParams }: Props) { const session = await auth(); const { page, tags } = await searchParams; - if (session?.user && !session.user.username) { - redirect("/create-username"); - } if (session?.user) { const activePunishment = await prisma.punishment.findFirst({ where: { diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index 881d010..bc20a1b 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -32,8 +32,8 @@ export default function PrivacyPage() {

The following types of information are stored when you use this website:

  • - Account Information: When you sign up or log in using Discord or Github, your username, e-mail, and profile picture are - collected. Your authentication tokens may also be temporarily stored to maintain your login session. + Account Information: When you sign up or log in using Discord or Github, your name, e-mail, and profile picture are collected. + Your authentication tokens may also be temporarily stored to maintain your login session.
  • Miis: We store any Miis you submit, including associated images (such as a picture of your Mii, QR codes, and custom images). @@ -77,7 +77,7 @@ export default function PrivacyPage() {

    • Errors and performance data is collected.
    • -
    • Only your user ID and username are sent, no other personally identifiable information is collected.
    • +
    • Only your user ID and name are sent, no other personally identifiable information is collected.
    • You can use ad blockers or browser privacy features to opt out.
    diff --git a/src/app/profile/[id]/page.tsx b/src/app/profile/[id]/page.tsx index 4140c3a..e8d90fc 100644 --- a/src/app/profile/[id]/page.tsx +++ b/src/app/profile/[id]/page.tsx @@ -39,24 +39,23 @@ export async function generateMetadata({ params }: Props): Promise { return { metadataBase: new URL(process.env.NEXT_PUBLIC_BASE_URL!), - title: `${user.name} (@${user.username}) - TomodachiShare`, + title: `${user.name} - TomodachiShare`, description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`, keywords: ["mii", "tomodachi life", "nintendo", "mii creator", "mii collection", "profile"], - creator: user.username, + creator: user.name, openGraph: { type: "profile", - title: `${user.name} (@${user.username}) - TomodachiShare`, + title: `${user.name} - TomodachiShare`, description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`, images: [user.image ?? "/guest.png"], - username: user.username, - firstName: user.name, + username: user.name, }, twitter: { card: "summary", - title: `${user.name} (@${user.username}) - TomodachiShare`, + title: `${user.name} - TomodachiShare`, description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`, images: [user.image ?? "/guest.png"], - creator: user.username!, + creator: user.name, }, alternates: { canonical: `/profile/${user.id}`, diff --git a/src/app/profile/settings/page.tsx b/src/app/profile/settings/page.tsx index 509623f..99cd240 100644 --- a/src/app/profile/settings/page.tsx +++ b/src/app/profile/settings/page.tsx @@ -21,7 +21,7 @@ export default async function ProfileSettingsPage() { if (!session) redirect("/login"); - const user = await prisma.user.findUnique({ where: { id: Number(session.user.id!) }, select: { description: true } }); + const user = await prisma.user.findUnique({ where: { id: Number(session.user?.id!) }, select: { description: true } }); return (
    diff --git a/src/app/robots.ts b/src/app/robots.ts index e05c087..f0bc168 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -5,18 +5,7 @@ export default function robots(): MetadataRoute.Robots { rules: { userAgent: "*", allow: "/", - disallow: [ - "/*?*page=", - "/profile*?*tags=", - "/create-username", - "/edit/*", - "/profile/settings", - "/random", - "/submit", - "/report/mii/*", - "/report/user/*", - "/admin", - ], + disallow: ["/*?*page=", "/profile*?*tags=", "/edit/*", "/profile/settings", "/random", "/submit", "/report/mii/*", "/report/user/*", "/admin"], }, sitemap: `${process.env.NEXT_PUBLIC_BASE_URL}/sitemap.xml`, }; diff --git a/src/app/submit/page.tsx b/src/app/submit/page.tsx index f2c63dd..8108992 100644 --- a/src/app/submit/page.tsx +++ b/src/app/submit/page.tsx @@ -22,10 +22,9 @@ export default async function SubmitPage() { const session = await auth(); if (!session) redirect("/login"); - if (!session.user.username) redirect("/create-username"); const activePunishment = await prisma.punishment.findFirst({ where: { - userId: Number(session?.user.id), + userId: Number(session?.user?.id), returned: false, }, }); diff --git a/src/components/admin/user-management.tsx b/src/components/admin/user-management.tsx index d0db95f..3734640 100644 --- a/src/components/admin/user-management.tsx +++ b/src/components/admin/user-management.tsx @@ -14,7 +14,6 @@ import PunishmentDeletionDialog from "./punishment-deletion-dialog"; interface ApiResponse { success: boolean; name: string; - username: string; image: string; createdAt: string; punishments: Prisma.PunishmentGetPayload<{ @@ -115,7 +114,7 @@ export default function Punishments() {

    {user.name}

    -

    @{user.username}

    +

    @{user.name}

    Created:{" "} {new Date(user.createdAt).toLocaleString("en-GB", { diff --git a/src/components/login-buttons.tsx b/src/components/login-buttons.tsx index 84a501c..16469bb 100644 --- a/src/components/login-buttons.tsx +++ b/src/components/login-buttons.tsx @@ -7,7 +7,7 @@ export default function LoginButtons() { return (