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 (
signIn("discord", { redirectTo: "/create-username" })}
+ onClick={() => signIn("discord", { redirectTo: "/" })}
aria-label="Login with Discord"
className="pill button gap-2 px-3! bg-indigo-400! border-indigo-500! hover:bg-indigo-500!"
>
@@ -15,7 +15,7 @@ export default function LoginButtons() {
Login with Discord
signIn("github", { redirectTo: "/create-username" })}
+ onClick={() => signIn("github", { redirectTo: "/" })}
aria-label="Login with GitHub"
className="pill button gap-2 px-3! bg-zinc-700! border-zinc-800! hover:bg-zinc-800! text-white"
>
diff --git a/src/components/mii-list/index.tsx b/src/components/mii-list/index.tsx
index 1e7f20e..0ef05f5 100644
--- a/src/components/mii-list/index.tsx
+++ b/src/components/mii-list/index.tsx
@@ -1,3 +1,4 @@
+import { headers } from "next/headers";
import Link from "next/link";
import { Prisma } from "@prisma/client";
@@ -25,7 +26,6 @@ interface Props {
export default async function MiiList({ searchParams, userId, inLikesPage }: Props) {
const session = await auth();
-
const parsed = searchSchema.safeParse(searchParams);
if (!parsed.success) return {parsed.error.issues[0].message} ;
@@ -34,7 +34,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
// My Likes page
let miiIdsLiked: number[] | undefined = undefined;
- if (inLikesPage && session?.user.id) {
+ if (inLikesPage && session?.user?.id) {
const likedMiis = await prisma.like.findMany({
where: { userId: Number(session.user.id) },
select: { miiId: true },
@@ -67,7 +67,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
user: {
select: {
id: true,
- username: true,
+ name: true,
},
},
}),
@@ -210,11 +210,11 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
{!userId && (
- @{mii.user?.username}
+ @{mii.user?.name}
)}
- {userId && Number(session?.user.id) == userId && (
+ {userId && Number(session?.user?.id) == userId && (
diff --git a/src/components/profile-information.tsx b/src/components/profile-information.tsx
index 0de8b8d..1ebec1d 100644
--- a/src/components/profile-information.tsx
+++ b/src/components/profile-information.tsx
@@ -15,7 +15,7 @@ interface Props {
export default async function ProfileInformation({ userId, page }: Props) {
const session = await auth();
- const id = userId ? userId : Number(session?.user.id);
+ const id = userId ? userId : Number(session?.user?.id);
const user = await prisma.user.findUnique({ where: { id } });
const likedMiis = await prisma.like.count({ where: { userId: id } });
@@ -23,7 +23,7 @@ export default async function ProfileInformation({ userId, page }: Props) {
const isAdmin = id === Number(process.env.NEXT_PUBLIC_ADMIN_USER_ID);
const isContributor = process.env.NEXT_PUBLIC_CONTRIBUTORS_USER_IDS?.split(",").includes(id.toString());
- const isOwnProfile = Number(session?.user.id) === id;
+ const isOwnProfile = Number(session?.user?.id) === id;
return (
@@ -47,7 +47,7 @@ export default async function ProfileInformation({ userId, page }: Props) {
)}
- @{user?.username}
+ ID: {user?.id}
diff --git a/src/components/profile-overview.tsx b/src/components/profile-overview.tsx
index f536f98..e33bca9 100644
--- a/src/components/profile-overview.tsx
+++ b/src/components/profile-overview.tsx
@@ -7,7 +7,7 @@ export default async function ProfileOverview() {
return (
-
+
- {session?.user?.username ?? "unknown"}
+ {session?.user?.name ?? "unknown"}
);
diff --git a/src/components/profile-settings/index.tsx b/src/components/profile-settings/index.tsx
index ca15926..6289e0e 100644
--- a/src/components/profile-settings/index.tsx
+++ b/src/components/profile-settings/index.tsx
@@ -2,9 +2,8 @@
import { useRouter } from "next/navigation";
import { useState } from "react";
-import dayjs from "dayjs";
-import { displayNameSchema, usernameSchema } from "@/lib/schemas";
+import { userNameSchema } from "@/lib/schemas";
import ProfilePictureSettings from "./profile-picture";
import SubmitDialogButton from "./submit-dialog-button";
@@ -19,14 +18,10 @@ export default function ProfileSettings({ currentDescription }: Props) {
const router = useRouter();
const [description, setDescription] = useState(currentDescription);
- const [displayName, setDisplayName] = useState("");
- const [username, setUsername] = useState("");
+ const [name, setName] = useState("");
const [descriptionChangeError, setDescriptionChangeError] = useState
(undefined);
- const [displayNameChangeError, setDisplayNameChangeError] = useState(undefined);
- const [usernameChangeError, setUsernameChangeError] = useState(undefined);
-
- const usernameDate = dayjs().add(90, "days");
+ const [nameChangeError, setNameChangeError] = useState(undefined);
const handleSubmitDescriptionChange = async (close: () => void) => {
const parsed = z.string().trim().max(256).safeParse(description);
@@ -51,45 +46,22 @@ export default function ProfileSettings({ currentDescription }: Props) {
router.refresh();
};
- const handleSubmitDisplayNameChange = async (close: () => void) => {
- const parsed = displayNameSchema.safeParse(displayName);
+ const handleSubmitNameChange = async (close: () => void) => {
+ const parsed = userNameSchema.safeParse(name);
if (!parsed.success) {
- setDisplayNameChangeError(parsed.error.issues[0].message);
+ setNameChangeError(parsed.error.issues[0].message);
return;
}
- const response = await fetch("/api/auth/display-name", {
+ const response = await fetch("/api/auth/name", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ displayName }),
+ body: JSON.stringify({ name }),
});
if (!response.ok) {
const { error } = await response.json();
- setDisplayNameChangeError(error);
- return;
- }
-
- close();
- router.refresh();
- };
-
- const handleSubmitUsernameChange = async (close: () => void) => {
- const parsed = usernameSchema.safeParse(username);
- if (!parsed.success) {
- setUsernameChangeError(parsed.error.issues[0].message);
- return;
- }
-
- const response = await fetch("/api/auth/username", {
- method: "PATCH",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ username }),
- });
-
- if (!response.ok) {
- const { error } = await response.json();
- setUsernameChangeError(error);
+ setNameChangeError(error);
return;
}
@@ -101,7 +73,7 @@ export default function ProfileSettings({ currentDescription }: Props) {
Profile Settings
-
Update your account info, and username.
+
Update your profile picture, description, name, etc.
{/* Separator */}
@@ -146,58 +118,21 @@ export default function ProfileSettings({ currentDescription }: Props) {
{/* Change Name */}
-
Change Display Name
-
This is a display name shown on your profile — feel free to change it anytime
+
Change Name
+
This is your name shown on your profile and miis — feel free to change it anytime
-
setDisplayName(e.target.value)} />
+
setName(e.target.value)} />
-
New display name:
-
'{displayName}'
-
-
-
-
-
- {/* Change Username */}
-
-
-
Change Username
-
Your unique tag on the site. Can only be changed once every 90 days
-
-
-
-
- setUsername(e.target.value)}
- />
- @
-
-
-
- After submitting, you can change it again on{" "}
- {usernameDate.toDate().toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })}.
-
-
-
-
New username:
-
'@{username}'
+
New name:
+
'{name}'
diff --git a/src/components/report/user-form.tsx b/src/components/report/user-form.tsx
index ddf894e..a0546c3 100644
--- a/src/components/report/user-form.tsx
+++ b/src/components/report/user-form.tsx
@@ -44,10 +44,7 @@ export default function ReportUserForm({ user }: Props) {
-
-
{user.name}
-
@{user.username}
-
+
{user.name}
diff --git a/src/components/username-form.tsx b/src/components/username-form.tsx
deleted file mode 100644
index 8795719..0000000
--- a/src/components/username-form.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { redirect } from "next/navigation";
-import { usernameSchema } from "@/lib/schemas";
-import SubmitButton from "./submit-button";
-
-export default function UsernameForm() {
- const [username, setUsername] = useState("");
- const [error, setError] = useState
(undefined);
-
- const handleSubmit = async () => {
- const parsed = usernameSchema.safeParse(username);
- if (!parsed.success) setError(parsed.error.issues[0].message);
-
- const response = await fetch("/api/auth/username", {
- method: "PATCH",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ username }),
- });
-
- if (!response.ok) {
- const { error } = await response.json();
- setError(error);
- return;
- }
-
- redirect("/");
- };
-
- return (
-
- );
-}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index 5cf4662..d08c7f2 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -15,7 +15,6 @@ export const { handlers, signIn, signOut, auth } = NextAuth({
async session({ session, user }) {
if (user) {
session.user.id = user.id;
- session.user.username = user.username;
session.user.email = user.email;
}
return session;
diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts
index 228301a..c59c8a7 100644
--- a/src/lib/rate-limit.ts
+++ b/src/lib/rate-limit.ts
@@ -102,7 +102,7 @@ export class RateLimit {
async handle(): Promise | undefined> {
const session = await auth();
const ip = this.request.headers.get("CF-Connecting-IP") || this.request.headers.get("X-Forwarded-For")?.split(",")[0];
- const identifier = (session ? session.user.id : ip) ?? "anonymous";
+ const identifier = (session ? session.user?.id : ip) ?? "anonymous";
this.data = await this.check(identifier);
diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts
index 7b05f96..91837ea 100644
--- a/src/lib/schemas.ts
+++ b/src/lib/schemas.ts
@@ -73,19 +73,11 @@ export const searchSchema = z.object({
seed: z.coerce.number({ error: "Seed must be a number" }).int({ error: "Seed must be an integer" }).optional(),
});
-// Account Info
-export const usernameSchema = z
+export const userNameSchema = z
.string()
.trim()
- .min(3, "Username must be at least 3 characters long")
- .max(20, "Username cannot be more than 20 characters long")
- .regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores");
-
-export const displayNameSchema = z
- .string()
- .trim()
- .min(2, { error: "Display name must be at least 2 characters long" })
- .max(64, { error: "Display name cannot be more than 64 characters long" })
+ .min(2, { error: "Name must be at least 2 characters long" })
+ .max(64, { error: "Name cannot be more than 64 characters long" })
.regex(/^[a-zA-Z0-9-_. ']+$/, {
- error: "Display name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
+ error: "Name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
});
diff --git a/src/types.d.ts b/src/types.d.ts
deleted file mode 100644
index 6e6497e..0000000
--- a/src/types.d.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Prisma } from "@prisma/client";
-import { DefaultSession } from "next-auth";
-
-declare module "next-auth" {
- interface Session {
- user: {
- username?: string;
- } & DefaultSession["user"];
- }
-
- interface User {
- username?: string;
- }
-}