diff --git a/src/app/admin/news/page.tsx b/src/app/admin/news/page.tsx index 0d97530..12682a9 100644 --- a/src/app/admin/news/page.tsx +++ b/src/app/admin/news/page.tsx @@ -122,12 +122,15 @@ export default function NewsEditorPage() { value={item.title} onChange={(v) => updateItem({ ...item, title: v })} /> - updateItem({ ...item, date: v })} - placeholder="2026-03-15" - /> +
+ + updateItem({ ...item, date: e.target.value })} + className="w-full rounded-lg border border-white/10 bg-neutral-800 px-4 py-2.5 text-white outline-none focus:border-gold transition-colors [color-scheme:dark]" + /> +
}; +function parseId(raw: string): number | null { + const n = Number(raw); + return Number.isInteger(n) && n > 0 ? n : null; +} + export async function GET(_request: NextRequest, { params }: Params) { const { id } = await params; - const member = getTeamMember(Number(id)); + const numId = parseId(id); + if (!numId) { + return NextResponse.json({ error: "Invalid ID" }, { status: 400 }); + } + const member = getTeamMember(numId); if (!member) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } @@ -15,15 +24,23 @@ export async function GET(_request: NextRequest, { params }: Params) { export async function PUT(request: NextRequest, { params }: Params) { const { id } = await params; + const numId = parseId(id); + if (!numId) { + return NextResponse.json({ error: "Invalid ID" }, { status: 400 }); + } const data = await request.json(); - updateTeamMember(Number(id), data); + updateTeamMember(numId, data); revalidatePath("/"); return NextResponse.json({ ok: true }); } export async function DELETE(_request: NextRequest, { params }: Params) { const { id } = await params; - deleteTeamMember(Number(id)); + const numId = parseId(id); + if (!numId) { + return NextResponse.json({ error: "Invalid ID" }, { status: 400 }); + } + deleteTeamMember(numId); revalidatePath("/"); return NextResponse.json({ ok: true }); } diff --git a/src/app/api/admin/team/reorder/route.ts b/src/app/api/admin/team/reorder/route.ts index b47e839..5489a22 100644 --- a/src/app/api/admin/team/reorder/route.ts +++ b/src/app/api/admin/team/reorder/route.ts @@ -5,8 +5,8 @@ import { revalidatePath } from "next/cache"; export async function PUT(request: NextRequest) { const { ids } = await request.json() as { ids: number[] }; - if (!Array.isArray(ids) || ids.length === 0) { - return NextResponse.json({ error: "ids array required" }, { status: 400 }); + if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => Number.isInteger(id) && id > 0)) { + return NextResponse.json({ error: "ids must be a non-empty array of positive integers" }, { status: 400 }); } reorderTeamMembers(ids); diff --git a/src/app/api/admin/upload/route.ts b/src/app/api/admin/upload/route.ts index 9d52048..2f4d1d3 100644 --- a/src/app/api/admin/upload/route.ts +++ b/src/app/api/admin/upload/route.ts @@ -3,12 +3,15 @@ import { writeFile, mkdir } from "fs/promises"; import path from "path"; const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp", "image/avif"]; +const ALLOWED_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".avif"]; +const ALLOWED_FOLDERS = ["team", "master-classes", "news", "classes"]; const MAX_SIZE = 5 * 1024 * 1024; // 5MB export async function POST(request: NextRequest) { const formData = await request.formData(); const file = formData.get("file") as File | null; - const folder = (formData.get("folder") as string) || "team"; + const rawFolder = (formData.get("folder") as string) || "team"; + const folder = ALLOWED_FOLDERS.includes(rawFolder) ? rawFolder : "team"; if (!file) { return NextResponse.json({ error: "No file provided" }, { status: 400 }); @@ -28,8 +31,14 @@ export async function POST(request: NextRequest) { ); } - // Sanitize filename - const ext = path.extname(file.name) || ".webp"; + // Validate and sanitize filename + const ext = path.extname(file.name).toLowerCase() || ".webp"; + if (!ALLOWED_EXTENSIONS.includes(ext)) { + return NextResponse.json( + { error: "Invalid file extension" }, + { status: 400 } + ); + } const baseName = file.name .replace(ext, "") .toLowerCase() diff --git a/src/app/api/master-class-register/route.ts b/src/app/api/master-class-register/route.ts index 8452a2c..650aa31 100644 --- a/src/app/api/master-class-register/route.ts +++ b/src/app/api/master-class-register/route.ts @@ -6,21 +6,24 @@ export async function POST(request: Request) { const body = await request.json(); const { masterClassTitle, name, instagram, telegram } = body; - if (!masterClassTitle || typeof masterClassTitle !== "string") { + if (!masterClassTitle || typeof masterClassTitle !== "string" || masterClassTitle.length > 200) { return NextResponse.json({ error: "masterClassTitle is required" }, { status: 400 }); } - if (!name || typeof name !== "string" || !name.trim()) { - return NextResponse.json({ error: "name is required" }, { status: 400 }); + if (!name || typeof name !== "string" || !name.trim() || name.length > 100) { + return NextResponse.json({ error: "name is required (max 100 chars)" }, { status: 400 }); } - if (!instagram || typeof instagram !== "string" || !instagram.trim()) { + if (!instagram || typeof instagram !== "string" || !instagram.trim() || instagram.length > 100) { return NextResponse.json({ error: "Instagram аккаунт обязателен" }, { status: 400 }); } + if (telegram && (typeof telegram !== "string" || telegram.length > 100)) { + return NextResponse.json({ error: "Telegram too long" }, { status: 400 }); + } const id = addMcRegistration( - masterClassTitle.trim(), - name.trim(), - instagram.trim(), - telegram && typeof telegram === "string" ? telegram.trim() : undefined + masterClassTitle.trim().slice(0, 200), + name.trim().slice(0, 100), + instagram.trim().slice(0, 100), + telegram && typeof telegram === "string" ? telegram.trim().slice(0, 100) : undefined ); return NextResponse.json({ ok: true, id }); diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 4551886..311c3ab 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -100,14 +100,14 @@ export function Header() { -