refactor: cleanup - schema edition
whole bunch of refactors (especially with schemas)
This commit is contained in:
parent
626016d689
commit
2e4611520d
7 changed files with 40 additions and 36 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { auth } from "@/lib/auth";
|
||||
|
|
@ -10,7 +10,7 @@ const usernameSchema = z
|
|||
.max(20, "Username cannot be more than 20 characters long")
|
||||
.regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores");
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
export async function PATCH(request: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { auth } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
const likeSchema = z.object({
|
||||
miiId: z.coerce.number().int({ message: "Mii ID must be an integer" }).positive({ message: "Mii ID must be valid" }),
|
||||
});
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
// todo: rate limit
|
||||
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const { miiId } = await request.json();
|
||||
if (!miiId) return NextResponse.json({ error: "Mii ID is required" }, { status: 400 });
|
||||
const body = await request.json();
|
||||
const parsed = likeSchema.safeParse(body);
|
||||
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.errors[0].message }, { status: 400 });
|
||||
const { miiId } = parsed.data;
|
||||
|
||||
const result = await prisma.$transaction(async (tx) => {
|
||||
const existingLike = await tx.like.findUnique({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
|
@ -17,31 +18,33 @@ import TomodachiLifeMii from "@/lib/tomodachi-life-mii";
|
|||
|
||||
const uploadsDirectory = path.join(process.cwd(), "public", "mii");
|
||||
|
||||
const submitSchema = z.object({
|
||||
name: nameSchema,
|
||||
tags: tagsSchema,
|
||||
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" }),
|
||||
image1: z.instanceof(File).optional(),
|
||||
image2: z.instanceof(File).optional(),
|
||||
image3: z.instanceof(File).optional(),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const formData = await request.formData();
|
||||
const parsed = submitSchema.safeParse({
|
||||
name: formData.get("name"),
|
||||
tags: JSON.parse(formData.get("tags") as string),
|
||||
qrBytesRaw: JSON.parse(formData.get("qrBytesRaw") as string),
|
||||
image1: formData.get("image1"),
|
||||
image2: formData.get("image2"),
|
||||
image3: formData.get("image3"),
|
||||
});
|
||||
|
||||
const name = formData.get("name") as string;
|
||||
const tags: string[] = JSON.parse(formData.get("tags") as string);
|
||||
const qrBytesRaw: number[] = JSON.parse(formData.get("qrBytesRaw") as string);
|
||||
|
||||
const image1 = formData.get("image1") as File;
|
||||
const image2 = formData.get("image2") as File;
|
||||
const image3 = formData.get("image3") as File;
|
||||
|
||||
if (!name) return NextResponse.json({ error: "Name is required" }, { status: 400 });
|
||||
if (!tags || tags.length == 0) return NextResponse.json({ error: "At least one tag is required" }, { status: 400 });
|
||||
if (!qrBytesRaw || qrBytesRaw.length == 0) return NextResponse.json({ error: "A QR code is required" }, { status: 400 });
|
||||
|
||||
const nameValidation = nameSchema.safeParse(name);
|
||||
if (!nameValidation.success) return NextResponse.json({ error: nameValidation.error.errors[0].message }, { status: 400 });
|
||||
|
||||
const tagsValidation = tagsSchema.safeParse(tags);
|
||||
if (!tagsValidation.success) return NextResponse.json({ error: tagsValidation.error.errors[0].message }, { status: 400 });
|
||||
|
||||
if (qrBytesRaw.length !== 372) return NextResponse.json({ error: "QR code size is not a valid Tomodachi Life QR code" }, { status: 400 });
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.errors[0].message }, { status: 400 });
|
||||
const { name, tags, qrBytesRaw, image1, image2, image3 } = parsed.data;
|
||||
|
||||
// Validate image files
|
||||
const images: File[] = [];
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ export default async function MiiList({ searchParams, userId, where }: Props) {
|
|||
: [];
|
||||
const whereTags = tagFilter.length > 0 ? { tags: { hasEvery: tagFilter } } : undefined;
|
||||
|
||||
// If the mii list is on a user's profile, don't query for the username
|
||||
const userInclude =
|
||||
userId == null
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -3,20 +3,13 @@
|
|||
import { useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { z } from "zod";
|
||||
|
||||
const searchSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(2)
|
||||
.max(64)
|
||||
.regex(/^[a-zA-Z0-9_]+$/);
|
||||
import { nameSchema } from "@/lib/schemas";
|
||||
|
||||
export default function SearchBar() {
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const handleSearch = () => {
|
||||
const result = searchSchema.safeParse(query);
|
||||
const result = nameSchema.safeParse(query);
|
||||
if (!result.success) redirect("/");
|
||||
|
||||
redirect(`/search?q=${query}`);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ interface Props {
|
|||
setTags: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}
|
||||
|
||||
const tagRegex = /^[a-z-]*$/;
|
||||
const tagRegex = /^[a-z0-9-_]*$/;
|
||||
const predefinedTags = ["anime", "art", "cartoon", "celebrity", "games", "history", "meme", "movie", "oc", "tv"];
|
||||
|
||||
export default function TagSelector({ tags, setTags }: Props) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { z } from "zod";
|
|||
|
||||
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" })
|
||||
.regex(/^[a-zA-Z0-9-_. ']+$/, {
|
||||
|
|
|
|||
Loading…
Reference in a new issue