From 52b47a4d43064f158871646925c7222243c29a40 Mon Sep 17 00:00:00 2001 From: Hikmet Date: Wed, 8 Jul 2026 07:50:28 +0300 Subject: [PATCH] Bootstrap configured admin users --- package.json | 2 +- prisma/bootstrap-admins.ts | 55 ++++++++++++++++++++++++++++++ src/app/api/auth/register/route.ts | 17 +++++---- src/lib/admin-emails.ts | 10 ++++++ 4 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 prisma/bootstrap-admins.ts create mode 100644 src/lib/admin-emails.ts diff --git a/package.json b/package.json index 5617f30..8a4d6f8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "next dev", "build": "prisma generate && next build", "start": "next start", - "start:deploy": "prisma db push && tsx prisma/seed-if-empty.ts && next start", + "start:deploy": "prisma db push && tsx prisma/seed-if-empty.ts && tsx prisma/bootstrap-admins.ts && next start", "lint": "next lint", "db:generate": "prisma generate", "db:migrate": "prisma migrate dev", diff --git a/prisma/bootstrap-admins.ts b/prisma/bootstrap-admins.ts new file mode 100644 index 0000000..cbb6853 --- /dev/null +++ b/prisma/bootstrap-admins.ts @@ -0,0 +1,55 @@ +import { PrismaClient, SubscriptionStatus, UserRole } from "@prisma/client"; +import { getConfiguredAdminEmails } from "../src/lib/admin-emails"; + +const prisma = new PrismaClient(); + +async function main() { + const adminEmails = getConfiguredAdminEmails(); + if (adminEmails.length === 0) { + console.log("No ADMIN_EMAILS configured. Skipping admin bootstrap."); + return; + } + + const premiumPlan = await prisma.plan.findUnique({ where: { code: "PREMIUM" } }); + if (!premiumPlan) { + throw new Error("PREMIUM plan is not seeded."); + } + + for (const email of adminEmails) { + const user = await prisma.user.findUnique({ where: { email } }); + if (!user) { + console.log(`Admin email ${email} is configured but user does not exist yet.`); + continue; + } + + await prisma.user.update({ + where: { id: user.id }, + data: { role: UserRole.ADMIN } + }); + + await prisma.subscription.upsert({ + where: { userId: user.id }, + update: { + planId: premiumPlan.id, + status: SubscriptionStatus.ACTIVE, + cancelAtPeriodEnd: false + }, + create: { + userId: user.id, + planId: premiumPlan.id, + status: SubscriptionStatus.ACTIVE + } + }); + + console.log(`Admin access ensured for ${email}.`); + } +} + +main() + .catch((error) => { + console.error(error); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts index aa32630..969b6f8 100644 --- a/src/app/api/auth/register/route.ts +++ b/src/app/api/auth/register/route.ts @@ -1,8 +1,10 @@ import bcrypt from "bcryptjs"; +import { UserRole } from "@prisma/client"; import { NextResponse } from "next/server"; import { z } from "zod"; import { prisma } from "@/lib/db"; import { createSession } from "@/lib/auth/session"; +import { isConfiguredAdminEmail } from "@/lib/admin-emails"; const schema = z.object({ email: z.string().email(), @@ -12,18 +14,21 @@ const schema = z.object({ export async function POST(req: Request) { const body = schema.parse(await req.json()); - const free = await prisma.plan.findUnique({ where: { code: "FREE" } }); - if (!free) return NextResponse.json({ error: "PLAN_NOT_SEEDED" }, { status: 500 }); + const email = body.email.trim().toLowerCase(); + const isAdmin = isConfiguredAdminEmail(email); + const plan = await prisma.plan.findUnique({ where: { code: isAdmin ? "PREMIUM" : "FREE" } }); + if (!plan) return NextResponse.json({ error: "PLAN_NOT_SEEDED" }, { status: 500 }); - const exists = await prisma.user.findUnique({ where: { email: body.email } }); + const exists = await prisma.user.findUnique({ where: { email } }); if (exists) return NextResponse.json({ error: "EMAIL_EXISTS" }, { status: 409 }); const user = await prisma.user.create({ data: { - email: body.email, - name: body.name || body.email.split("@")[0], + email, + name: body.name || email.split("@")[0], passwordHash: await bcrypt.hash(body.password, 10), - subscription: { create: { planId: free.id, status: "ACTIVE" } } + role: isAdmin ? UserRole.ADMIN : UserRole.USER, + subscription: { create: { planId: plan.id, status: "ACTIVE" } } } }); await createSession(user.id); diff --git a/src/lib/admin-emails.ts b/src/lib/admin-emails.ts new file mode 100644 index 0000000..0611077 --- /dev/null +++ b/src/lib/admin-emails.ts @@ -0,0 +1,10 @@ +export function getConfiguredAdminEmails() { + return (process.env.ADMIN_EMAILS || "") + .split(/[\s,;]+/) + .map((email) => email.trim().toLowerCase()) + .filter(Boolean); +} + +export function isConfiguredAdminEmail(email: string) { + return getConfiguredAdminEmails().includes(email.trim().toLowerCase()); +}