chore: update packages
also migrate zod to v4
This commit is contained in:
parent
afb73ec3a6
commit
8b4842b584
20 changed files with 918 additions and 922 deletions
28
package.json
28
package.json
|
|
@ -13,10 +13,10 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@2toad/profanity": "^3.1.1",
|
||||
"@auth/prisma-adapter": "2.9.1",
|
||||
"@auth/prisma-adapter": "2.10.0",
|
||||
"@bprogress/next": "^3.2.12",
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@prisma/client": "^6.9.0",
|
||||
"@prisma/client": "^6.11.1",
|
||||
"@types/sjcl": "^1.0.34",
|
||||
"bit-buffer": "^0.2.5",
|
||||
"canvas-confetti": "^1.9.3",
|
||||
|
|
@ -26,32 +26,32 @@
|
|||
"file-type": "^21.0.0",
|
||||
"ioredis": "^5.6.1",
|
||||
"jsqr": "^1.4.0",
|
||||
"next": "15.3.3",
|
||||
"next": "15.3.5",
|
||||
"next-auth": "5.0.0-beta.25",
|
||||
"qrcode-generator": "^1.5.0",
|
||||
"qrcode-generator": "^2.0.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-webcam": "^7.2.0",
|
||||
"satori": "^0.15.2",
|
||||
"sharp": "^0.34.2",
|
||||
"sharp": "^0.34.3",
|
||||
"sjcl-with-all": "1.0.8",
|
||||
"swr": "^2.3.3",
|
||||
"zod": "^3.25.63"
|
||||
"swr": "^2.3.4",
|
||||
"zod": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@iconify/react": "^6.0.0",
|
||||
"@tailwindcss/postcss": "^4.1.10",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"@types/node": "^24.0.13",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-config-next": "15.3.3",
|
||||
"prisma": "^6.9.0",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"eslint": "^9.31.0",
|
||||
"eslint-config-next": "15.3.5",
|
||||
"prisma": "^6.11.1",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.3"
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1697
pnpm-lock.yaml
1697
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@ export async function GET(request: NextRequest) {
|
|||
const searchParams = request.nextUrl.searchParams;
|
||||
const parsed = idSchema.safeParse(searchParams.get("id"));
|
||||
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.errors[0].message }, { status: 400 });
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.issues[0].message }, { status: 400 });
|
||||
const userId = parsed.data;
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
|
|
|
|||
|
|
@ -11,18 +11,15 @@ import { PunishmentType } from "@prisma/client";
|
|||
const punishSchema = z.object({
|
||||
type: z.enum([PunishmentType.WARNING, PunishmentType.TEMP_EXILE, PunishmentType.PERM_EXILE]),
|
||||
duration: z
|
||||
.number({ message: "Duration (days) must be a number" })
|
||||
.int({ message: "Duration (days) must be an integer" })
|
||||
.positive({ message: "Duration (days) must be valid" }),
|
||||
.number({ error: "Duration (days) must be a number" })
|
||||
.int({ error: "Duration (days) must be an integer" })
|
||||
.positive({ error: "Duration (days) must be valid" }),
|
||||
notes: z.string(),
|
||||
reasons: z.array(z.string()).optional(),
|
||||
miiReasons: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z
|
||||
.number({ message: "Mii ID must be a number" })
|
||||
.int({ message: "Mii ID must be an integer" })
|
||||
.positive({ message: "Mii ID must be valid" }),
|
||||
id: z.number({ error: "Mii ID must be a number" }).int({ error: "Mii ID must be an integer" }).positive({ error: "Mii ID must be valid" }),
|
||||
reason: z.string(),
|
||||
})
|
||||
)
|
||||
|
|
@ -38,13 +35,13 @@ export async function POST(request: NextRequest) {
|
|||
const searchParams = request.nextUrl.searchParams;
|
||||
const parsedUserId = idSchema.safeParse(searchParams.get("id"));
|
||||
|
||||
if (!parsedUserId.success) return NextResponse.json({ error: parsedUserId.error.errors[0].message }, { status: 400 });
|
||||
if (!parsedUserId.success) return NextResponse.json({ error: parsedUserId.error.issues[0].message }, { status: 400 });
|
||||
const userId = parsedUserId.data;
|
||||
|
||||
const body = await request.json();
|
||||
const parsed = punishSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.errors[0].message }, { status: 400 });
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.issues[0].message }, { status: 400 });
|
||||
const { type, duration, notes, reasons, miiReasons } = parsed.data;
|
||||
|
||||
const expiresAt = type === "TEMP_EXILE" ? dayjs().add(duration, "days").toDate() : null;
|
||||
|
|
@ -77,7 +74,7 @@ export async function DELETE(request: NextRequest) {
|
|||
const searchParams = request.nextUrl.searchParams;
|
||||
const parsedPunishmentId = idSchema.safeParse(searchParams.get("id"));
|
||||
|
||||
if (!parsedPunishmentId.success) return NextResponse.json({ error: parsedPunishmentId.error.errors[0].message }, { status: 400 });
|
||||
if (!parsedPunishmentId.success) return NextResponse.json({ error: parsedPunishmentId.error.issues[0].message }, { status: 400 });
|
||||
const punishmentId = parsedPunishmentId.data;
|
||||
|
||||
await prisma.punishment.delete({
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export async function PATCH(request: NextRequest) {
|
|||
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.errors[0].message }, 400);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export async function PATCH(request: NextRequest) {
|
|||
image: formData.get("image"),
|
||||
});
|
||||
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const { image } = parsed.data;
|
||||
|
||||
// If there is no image, set the profile picture to the guest image
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export async function PATCH(request: NextRequest) {
|
|||
}
|
||||
|
||||
const validation = usernameSchema.safeParse(username);
|
||||
if (!validation.success) return rateLimit.sendResponse({ error: validation.error.errors[0].message }, 400);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export async function DELETE(request: NextRequest, { params }: { params: Promise
|
|||
|
||||
const { id: slugId } = await params;
|
||||
const parsed = idSchema.safeParse(slugId);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const miiId = parsed.data;
|
||||
|
||||
// Check ownership of Mii
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
// Get Mii ID
|
||||
const { id: slugId } = await params;
|
||||
const parsedId = idSchema.safeParse(slugId);
|
||||
if (!parsedId.success) return rateLimit.sendResponse({ error: parsedId.error.errors[0].message }, 400);
|
||||
if (!parsedId.success) return rateLimit.sendResponse({ error: parsedId.error.issues[0].message }, 400);
|
||||
const miiId = parsedId.data;
|
||||
|
||||
// Check ownership of Mii
|
||||
|
|
@ -78,7 +78,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
image3: formData.get("image3"),
|
||||
});
|
||||
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const { name, tags, description, image1, image2, image3 } = parsed.data;
|
||||
|
||||
// Validate image files
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
|
||||
const { id: slugId } = await params;
|
||||
const parsed = idSchema.safeParse(slugId);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const miiId = parsed.data;
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import { RateLimit } from "@/lib/rate-limit";
|
|||
import { MiiWithUsername } from "@/types";
|
||||
|
||||
const reportSchema = z.object({
|
||||
id: z.coerce.number({ message: "ID must be a number" }).int({ message: "ID must be an integer" }).positive({ message: "ID must be valid" }),
|
||||
type: z.enum(["mii", "user"], { message: "Type must be either 'mii' or 'user'" }),
|
||||
id: z.coerce.number({ error: "ID must be a number" }).int({ error: "ID must be an integer" }).positive({ error: "ID must be valid" }),
|
||||
type: z.enum(["mii", "user"], { error: "Type must be either 'mii' or 'user'" }),
|
||||
reason: z.enum(["inappropriate", "spam", "copyright", "other"], {
|
||||
message: "Reason must be either 'inappropriate', 'spam', 'copyright', or 'other'",
|
||||
}),
|
||||
|
|
@ -27,7 +27,7 @@ export async function POST(request: NextRequest) {
|
|||
const body = await request.json();
|
||||
const parsed = reportSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const { id, type, reason, notes } = parsed.data;
|
||||
|
||||
let mii: MiiWithUsername | null = null;
|
||||
|
|
|
|||
|
|
@ -25,9 +25,7 @@ const submitSchema = z.object({
|
|||
name: nameSchema,
|
||||
tags: tagsSchema,
|
||||
description: z.string().trim().max(256).optional(),
|
||||
qrBytesRaw: z
|
||||
.array(z.number(), { required_error: "A QR code is required" })
|
||||
.length(372, { message: "QR code size is not a valid Tomodachi Life QR code" }),
|
||||
qrBytesRaw: z.array(z.number(), { error: "A QR code is required" }).length(372, { error: "QR code size is not a valid Tomodachi Life QR code" }),
|
||||
image1: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
image2: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
image3: z.union([z.instanceof(File), z.any()]).optional(),
|
||||
|
|
@ -67,7 +65,7 @@ export async function POST(request: NextRequest) {
|
|||
image3: formData.get("image3"),
|
||||
});
|
||||
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const { name: uncensoredName, tags: uncensoredTags, description: uncensoredDescription, qrBytesRaw, image1, image2, image3 } = parsed.data;
|
||||
|
||||
// Censor potential inappropriate words
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||
|
||||
const { id: slugId } = await params;
|
||||
const parsed = idSchema.safeParse(slugId);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const miiId = parsed.data;
|
||||
|
||||
const searchParamsParsed = searchParamsSchema.safeParse(Object.fromEntries(request.nextUrl.searchParams));
|
||||
if (!searchParamsParsed.success) return rateLimit.sendResponse({ error: searchParamsParsed.error.errors[0].message }, 400);
|
||||
if (!searchParamsParsed.success) return rateLimit.sendResponse({ error: searchParamsParsed.error.issues[0].message }, 400);
|
||||
const { type: imageType } = searchParamsParsed.data;
|
||||
|
||||
const fileExtension = imageType === "metadata" ? ".png" : ".webp";
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||
|
||||
const { id: slugId } = await params;
|
||||
const parsed = idSchema.safeParse(slugId);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.errors[0].message }, 400);
|
||||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const userId = parsed.data;
|
||||
|
||||
const filePath = path.join(process.cwd(), "uploads", "user", `${userId}.webp`);
|
||||
|
|
|
|||
|
|
@ -36,15 +36,15 @@ const searchSchema = z.object({
|
|||
// todo: incorporate tagsSchema
|
||||
// Pages
|
||||
limit: z.coerce
|
||||
.number({ message: "Limit must be a number" })
|
||||
.int({ message: "Limit must be an integer" })
|
||||
.min(1, { message: "Limit must be at least 1" })
|
||||
.max(100, { message: "Limit cannot be more than 100" })
|
||||
.number({ error: "Limit must be a number" })
|
||||
.int({ error: "Limit must be an integer" })
|
||||
.min(1, { error: "Limit must be at least 1" })
|
||||
.max(100, { error: "Limit cannot be more than 100" })
|
||||
.optional(),
|
||||
page: z.coerce
|
||||
.number({ message: "Page must be a number" })
|
||||
.int({ message: "Page must be an integer" })
|
||||
.min(1, { message: "Page must be at least 1" })
|
||||
.number({ error: "Page must be a number" })
|
||||
.int({ error: "Page must be an integer" })
|
||||
.min(1, { error: "Page must be at least 1" })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ export default async function MiiList({ searchParams, userId, inLikesPage }: Pro
|
|||
const session = await auth();
|
||||
|
||||
const parsed = searchSchema.safeParse(searchParams);
|
||||
if (!parsed.success) return <h1>{parsed.error.errors[0].message}</h1>;
|
||||
if (!parsed.success) return <h1>{parsed.error.issues[0].message}</h1>;
|
||||
|
||||
const { q: query, sort, tags, page = 1, limit = 24 } = parsed.data;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export default function ProfileSettings() {
|
|||
const handleSubmitDisplayNameChange = async (close: () => void) => {
|
||||
const parsed = displayNameSchema.safeParse(displayName);
|
||||
if (!parsed.success) {
|
||||
setDisplayNameChangeError(parsed.error.errors[0].message);
|
||||
setDisplayNameChangeError(parsed.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export default function ProfileSettings() {
|
|||
const handleSubmitUsernameChange = async (close: () => void) => {
|
||||
const parsed = usernameSchema.safeParse(username);
|
||||
if (!parsed.success) {
|
||||
setUsernameChangeError(parsed.error.errors[0].message);
|
||||
setUsernameChangeError(parsed.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,12 +44,12 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
// Validate before sending request
|
||||
const nameValidation = nameSchema.safeParse(name);
|
||||
if (!nameValidation.success) {
|
||||
setError(nameValidation.error.errors[0].message);
|
||||
setError(nameValidation.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
const tagsValidation = tagsSchema.safeParse(tags);
|
||||
if (!tagsValidation.success) {
|
||||
setError(tagsValidation.error.errors[0].message);
|
||||
setError(tagsValidation.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,12 +49,12 @@ export default function SubmitForm() {
|
|||
// Validate before sending request
|
||||
const nameValidation = nameSchema.safeParse(name);
|
||||
if (!nameValidation.success) {
|
||||
setError(nameValidation.error.errors[0].message);
|
||||
setError(nameValidation.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
const tagsValidation = tagsSchema.safeParse(tags);
|
||||
if (!tagsValidation.success) {
|
||||
setError(tagsValidation.error.errors[0].message);
|
||||
setError(tagsValidation.error.issues[0].message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function UsernameForm() {
|
|||
|
||||
const handleSubmit = async () => {
|
||||
const parsed = usernameSchema.safeParse(username);
|
||||
if (!parsed.success) setError(parsed.error.errors[0].message);
|
||||
if (!parsed.success) setError(parsed.error.issues[0].message);
|
||||
|
||||
const response = await fetch("/api/auth/username", {
|
||||
method: "PATCH",
|
||||
|
|
|
|||
|
|
@ -5,39 +5,39 @@ import { z } from "zod";
|
|||
export const querySchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(2, { message: "Search query must be at least 2 characters long" })
|
||||
.max(64, { message: "Search query cannot be more than 64 characters long" })
|
||||
.min(2, { error: "Search query must be at least 2 characters long" })
|
||||
.max(64, { error: "Search query cannot be more than 64 characters long" })
|
||||
.regex(/^[a-zA-Z0-9-_. ']+$/, {
|
||||
message: "Search query can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||
error: "Search query can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||
});
|
||||
|
||||
// Miis
|
||||
export const nameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(2, { message: "Name must be at least 2 characters long" })
|
||||
.max(64, { message: "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-_. ']+$/, {
|
||||
message: "Name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||
error: "Name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||
});
|
||||
|
||||
export const tagsSchema = z
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.min(2, { message: "Tags must be at least 2 characters long" })
|
||||
.max(64, { message: "Tags cannot be more than 20 characters long" })
|
||||
.min(2, { error: "Tags must be at least 2 characters long" })
|
||||
.max(64, { error: "Tags cannot be more than 20 characters long" })
|
||||
.regex(/^[a-z0-9-_]+$/, {
|
||||
message: "Tags can only contain lowercase letters, numbers, dashes, and underscores.",
|
||||
error: "Tags can only contain lowercase letters, numbers, dashes, and underscores.",
|
||||
})
|
||||
)
|
||||
.min(1, { message: "There must be at least 1 tag" })
|
||||
.max(8, { message: "There cannot be more than 8 tags" });
|
||||
.min(1, { error: "There must be at least 1 tag" })
|
||||
.max(8, { error: "There cannot be more than 8 tags" });
|
||||
|
||||
export const idSchema = z.coerce
|
||||
.number({ message: "ID must be a number" })
|
||||
.int({ message: "ID must be an integer" })
|
||||
.positive({ message: "ID must be valid" });
|
||||
.number({ error: "ID must be a number" })
|
||||
.int({ error: "ID must be an integer" })
|
||||
.positive({ error: "ID must be valid" });
|
||||
|
||||
// Account Info
|
||||
export const usernameSchema = z
|
||||
|
|
@ -50,8 +50,8 @@ export const usernameSchema = z
|
|||
export const displayNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(2, { message: "Display name must be at least 2 characters long" })
|
||||
.max(64, { message: "Display name cannot be more than 64 characters long" })
|
||||
.min(2, { error: "Display name must be at least 2 characters long" })
|
||||
.max(64, { error: "Display name cannot be more than 64 characters long" })
|
||||
.regex(/^[a-zA-Z0-9-_. ']+$/, {
|
||||
message: "Display name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||
error: "Display name can only contain letters, numbers, dashes, underscores, apostrophes, and spaces.",
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue