Initial push

This commit is contained in:
2026-03-08 15:49:34 +01:00
parent 8da12bb7d1
commit 47127f276d
101 changed files with 13844 additions and 8 deletions

91
app/api/team/route.ts Normal file
View File

@@ -0,0 +1,91 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { users, invitations } from "@/lib/db/schema";
import { checkRateLimit, getClientIp } from "@/lib/security/rateLimit";
import { sendInvitationEmail } from "@/lib/email";
import { z } from "zod";
import { nanoid } from "nanoid";
import { eq, ne } from "drizzle-orm";
const InviteSchema = z.object({
email: z.string().email().max(254),
role: z.enum(["admin", "moderator"]),
playerUuid: z.string().optional(),
});
export async function GET(req: NextRequest) {
const session = await auth.api.getSession({ headers: req.headers });
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (session.user.role !== "superadmin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const teamUsers = await db
.select()
.from(users)
.where(ne(users.id, session.user.id));
const pendingInvites = await db
.select()
.from(invitations)
.where(eq(invitations.acceptedAt, null as unknown as number));
return NextResponse.json({ users: teamUsers, pendingInvites });
}
export async function POST(req: NextRequest) {
const session = await auth.api.getSession({ headers: req.headers });
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (session.user.role !== "superadmin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const ip = getClientIp(req);
const { allowed } = checkRateLimit(ip, 10);
if (!allowed) return NextResponse.json({ error: "Too many requests" }, { status: 429 });
let body: z.infer<typeof InviteSchema>;
try {
body = InviteSchema.parse(await req.json());
} catch {
return NextResponse.json({ error: "Invalid request" }, { status: 400 });
}
// Check if user already exists
const existing = await db.select().from(users).where(eq(users.email, body.email)).get();
if (existing) {
return NextResponse.json({ error: "User already exists" }, { status: 409 });
}
// Create invitation
const token = nanoid(48);
const expiresAt = Date.now() + 48 * 60 * 60 * 1000; // 48 hours
await db.insert(invitations).values({
id: nanoid(),
email: body.email,
role: body.role,
invitedBy: session.user.id,
token,
expiresAt,
createdAt: Date.now(),
});
const baseUrl = process.env.BETTER_AUTH_URL ?? "http://localhost:3000";
const inviteUrl = `${baseUrl}/accept-invite?token=${token}`;
try {
await sendInvitationEmail({
to: body.email,
invitedByName: session.user.name ?? "An admin",
inviteUrl,
role: body.role,
});
} catch (err) {
// Log but don't fail — admin can resend
console.error("[Team] Failed to send invitation email:", err);
}
return NextResponse.json({ success: true, inviteUrl }, { status: 201 });
}