feat(01-02): login page UI, logout flow, seed script, and auth unit tests
- src/app/(auth)/layout.tsx: centered auth layout for login page - src/app/(auth)/login/page.tsx: login form with error/loading states, sign up link - src/components/providers.tsx: SessionProvider wrapper for client-side session - src/components/layout/header.tsx: authenticated header with Sign out button - src/app/(dashboard)/layout.tsx: dashboard layout wrapping Header component - src/app/(dashboard)/dashboard/page.tsx: basic dashboard page post-login - src/app/layout.tsx: wrap root with SessionProvider via Providers component - prisma/seed.ts: idempotent seed for Demo ISP tenant + admin + super-admin users - package.json: add db:seed script and prisma.seed config, add tsx devDep - src/lib/__tests__/auth.test.ts: 8 unit tests for authOptions callbacks
This commit is contained in:
97
prisma/seed.ts
Normal file
97
prisma/seed.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { PrismaClient, TenantStatus } from "@prisma/client";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log("Seeding database...");
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tenant: Demo ISP
|
||||
// -----------------------------------------------------------------------
|
||||
const demoTenant = await prisma.tenant.upsert({
|
||||
where: { slug: "demo-isp" },
|
||||
update: {},
|
||||
create: {
|
||||
name: "Demo ISP",
|
||||
slug: "demo-isp",
|
||||
ownerEmail: "admin@demo.com",
|
||||
status: TenantStatus.ACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Tenant upserted: ${demoTenant.name} (${demoTenant.id})`);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Admin user: admin@demo.com / admin123
|
||||
// Scoped to Demo ISP tenant
|
||||
// -----------------------------------------------------------------------
|
||||
const adminPasswordHash = await bcrypt.hash("admin123", 12);
|
||||
|
||||
const adminUser = await prisma.user.upsert({
|
||||
where: {
|
||||
email_tenantId: {
|
||||
email: "admin@demo.com",
|
||||
tenantId: demoTenant.id,
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
email: "admin@demo.com",
|
||||
passwordHash: adminPasswordHash,
|
||||
firstName: "Demo",
|
||||
lastName: "Admin",
|
||||
tenantId: demoTenant.id,
|
||||
roles: ["ADMIN"],
|
||||
isActive: true,
|
||||
isSuperAdmin: false,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Admin user upserted: ${adminUser.email} (${adminUser.id})`);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Super-admin: superadmin@netforge.com / super123
|
||||
// No tenant — platform-wide access
|
||||
// -----------------------------------------------------------------------
|
||||
const superAdminPasswordHash = await bcrypt.hash("super123", 12);
|
||||
|
||||
// Super-admin has tenantId=null — use findFirst + create pattern since
|
||||
// @@unique([email, tenantId]) with null tenantId behaves differently across DBs
|
||||
const existingSuperAdmin = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: "superadmin@netforge.com",
|
||||
tenantId: null,
|
||||
},
|
||||
});
|
||||
|
||||
const superAdmin = existingSuperAdmin
|
||||
? existingSuperAdmin
|
||||
: await prisma.user.create({
|
||||
data: {
|
||||
email: "superadmin@netforge.com",
|
||||
passwordHash: superAdminPasswordHash,
|
||||
firstName: "Super",
|
||||
lastName: "Admin",
|
||||
tenantId: null,
|
||||
roles: [],
|
||||
isActive: true,
|
||||
isSuperAdmin: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Super-admin upserted: ${superAdmin.email} (${superAdmin.id})`);
|
||||
|
||||
console.log("\nSeed complete. Test credentials:");
|
||||
console.log(" Admin: admin@demo.com / admin123");
|
||||
console.log(" Super-admin: superadmin@netforge.com / super123");
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
Reference in New Issue
Block a user