From 91208e44a1167cbbdea361db26c19fc3305ca917 Mon Sep 17 00:00:00 2001 From: Ahmet Kilinc Date: Sat, 20 Dec 2025 14:06:02 +0000 Subject: [PATCH] rate limit and auth checks --- actions/repositories.ts | 4 +- app/(main)/new/form.tsx | 169 ++++++++++++++++++++++++ app/(main)/new/page.tsx | 203 ++--------------------------- app/api/avatar/[filename]/route.ts | 9 ++ app/api/file/[...path]/route.ts | 21 ++- app/api/git/[...path]/route.ts | 87 +++++-------- lib/api-auth.ts | 69 ++++++++++ lib/r2-fs.ts | 2 + lib/rate-limit.ts | 96 ++++++++++++++ 9 files changed, 403 insertions(+), 257 deletions(-) create mode 100644 app/(main)/new/form.tsx create mode 100644 lib/api-auth.ts create mode 100644 lib/rate-limit.ts diff --git a/actions/repositories.ts b/actions/repositories.ts index 918a84f..182da4d 100644 --- a/actions/repositories.ts +++ b/actions/repositories.ts @@ -816,7 +816,9 @@ export async function getPublicUsers(sortBy: "newest" | "oldest" = "newest", lim avatarUrl: users.avatarUrl, bio: users.bio, createdAt: users.createdAt, - repoCount: sql`(SELECT COUNT(*) FROM repositories WHERE repositories.owner_id = users.id AND repositories.visibility = 'public')`.as("repo_count"), + repoCount: sql`(SELECT COUNT(*) FROM repositories WHERE repositories.owner_id = users.id AND repositories.visibility = 'public')`.as( + "repo_count" + ), }) .from(users) .orderBy(sortBy === "newest" ? desc(users.createdAt) : users.createdAt) diff --git a/app/(main)/new/form.tsx b/app/(main)/new/form.tsx new file mode 100644 index 0000000..abdf34d --- /dev/null +++ b/app/(main)/new/form.tsx @@ -0,0 +1,169 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { createRepository } from "@/actions/repositories"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { toast } from "sonner"; +import { Loader2, Lock, Globe } from "lucide-react"; +import Link from "next/link"; + +export function NewRepoForm({ username }: { username: string }) { + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + name: "", + description: "", + visibility: "public" as "public" | "private", + }); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + + try { + await createRepository({ + name: formData.name, + description: formData.description || undefined, + visibility: formData.visibility, + }); + + toast.success("Repository created!"); + router.push(`/${username}/${formData.name.toLowerCase().replace(/\s+/g, "-")}`); + } catch (err) { + toast.error(err instanceof Error ? err.message : "Failed to create repository"); + } finally { + setLoading(false); + } + } + + return ( +
+
+

Create a new repository

+

A repository contains all project files, including the revision history.

+
+ +
+
+
+ +
+ {username} + / + setFormData({ ...formData, name: e.target.value })} + placeholder="my-awesome-project" + required + pattern="^[a-zA-Z0-9_.-]+$" + className="flex-1 bg-input/50" + /> +
+

Great repository names are short and memorable.

+
+ +
+ +