Bootstrap configured admin users

This commit is contained in:
2026-07-08 07:50:28 +03:00
parent 18aa62d7e1
commit 52b47a4d43
4 changed files with 77 additions and 7 deletions
+1 -1
View File
@@ -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",
+55
View File
@@ -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();
});
+11 -6
View File
@@ -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);
+10
View File
@@ -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());
}