Initial commit

Next.js + Express event management app for Hope Family Church.
This commit is contained in:
2026-07-23 15:26:47 +02:00
commit 3d381944d2
246 changed files with 57565 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
/**
* Seed script: inserts 75 demo users (mix of roles, ~25 inactive)
* Run: node scripts/seed-users.js
*/
const { PrismaClient } = require('@prisma/client');
const bcrypt = require('bcryptjs');
const prisma = new PrismaClient();
const firstNames = [
'Liam', 'Olivia', 'Noah', 'Emma', 'James', 'Ava', 'Elijah', 'Sophia',
'Lucas', 'Isabella', 'Mason', 'Mia', 'Logan', 'Charlotte', 'Ethan',
'Amelia', 'Aiden', 'Harper', 'Jackson', 'Evelyn', 'Thabo', 'Zanele',
'Sipho', 'Nomsa', 'Lebo', 'Thandeka', 'Kagiso', 'Palesa', 'Bongani',
'Ayanda', 'Pieter', 'Anri', 'Francois', 'Elizma', 'Hendrik', 'Marius',
'Chloé', 'Dylan', 'Amber', 'Caleb', 'Zoe', 'Hunter', 'Lily', 'Owen',
'Grace', 'Ryan', 'Chloe', 'Nathan', 'Stella', 'Connor', 'Hazel',
'Miles', 'Violet', 'Eli', 'Aurora', 'Isaiah', 'Luna', 'Nolan',
'Scarlett', 'Wyatt', 'Penelope', 'Sebastian', 'Layla', 'Asher',
'Riley', 'Oliver', 'Zoey', 'Ezra', 'Nora', 'Micah', 'Hannah',
'Jonah', 'Leah', 'Aaron', 'Aubrey', 'Charles',
];
const lastNames = [
'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Garcia', 'Miller',
'Davis', 'Wilson', 'Moore', 'Taylor', 'Anderson', 'Thomas', 'Jackson',
'White', 'Harris', 'Martin', 'Thompson', 'Robinson', 'Lewis',
'Dlamini', 'Nkosi', 'Mokoena', 'Ndlovu', 'Mthembu', 'Zulu', 'Nxumalo',
'du Plessis', 'van der Berg', 'Botha', 'Steyn', 'Fourie', 'Pretorius',
'Venter', 'Coetzee', 'van Niekerk', 'Lombard', 'Janse van Rensburg',
'Walker', 'Hall', 'Allen', 'Young', 'Hernandez', 'King', 'Wright',
'Scott', 'Green', 'Baker', 'Adams', 'Nelson', 'Carter', 'Mitchell',
'Perez', 'Roberts', 'Turner', 'Phillips', 'Campbell', 'Parker', 'Evans',
'Edwards', 'Collins', 'Stewart', 'Morris', 'Reed', 'Cook', 'Morgan',
];
function pick(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function randomPhone() {
// SA mobile format: 07x/08x xxxxxxx
const prefixes = ['071', '072', '073', '074', '076', '078', '079', '081', '082', '083', '084'];
return `${pick(prefixes)}${String(Math.floor(Math.random() * 9000000) + 1000000)}`;
}
async function main() {
const passwordHash = await bcrypt.hash('Demo@1234', 10);
// Role distribution: ~55 user, ~15 staff, ~5 supervisor
const rolePool = [
...Array(55).fill('user'),
...Array(15).fill('staff'),
...Array(5).fill('supervisor'),
];
const notifPool = ['email', 'email', 'email', 'whatsapp', 'both'];
const usedEmails = new Set();
const usedPhones = new Set();
const users = [];
// Build 75 user records
while (users.length < 75) {
const first = pick(firstNames);
const last = pick(lastNames);
const tag = Math.floor(Math.random() * 999) + 1;
const email = `${first.toLowerCase().replace(/ /g, '')}.${last.toLowerCase().replace(/ /g, '').replace(/'/g, '')}${tag}@demo.church`;
if (usedEmails.has(email)) continue;
usedEmails.add(email);
// ~60% of users have a phone number
let phone = null;
if (Math.random() < 0.6) {
let attempt = randomPhone();
let tries = 0;
while (usedPhones.has(attempt) && tries < 20) {
attempt = randomPhone();
tries++;
}
if (!usedPhones.has(attempt)) {
usedPhones.add(attempt);
phone = attempt;
}
}
users.push({
name: `${first} ${last}`,
email,
password: passwordHash,
role: pick(rolePool),
isActive: true, // will override 25 below
phoneNumber: phone,
notificationPreference: pick(notifPool),
});
}
// Mark exactly 25 as inactive (spread across the list)
const inactiveIndexes = new Set();
while (inactiveIndexes.size < 25) {
inactiveIndexes.add(Math.floor(Math.random() * 75));
}
inactiveIndexes.forEach(i => { users[i].isActive = false; });
console.log(`Inserting ${users.length} users…`);
console.log(` Active: ${users.filter(u => u.isActive).length}`);
console.log(` Inactive: ${users.filter(u => !u.isActive).length}`);
console.log(` Roles: user=${users.filter(u => u.role === 'user').length}, staff=${users.filter(u => u.role === 'staff').length}, supervisor=${users.filter(u => u.role === 'supervisor').length}`);
let created = 0;
let skipped = 0;
for (const user of users) {
try {
await prisma.user.create({ data: user });
created++;
} catch (err) {
if (err.code === 'P2002') {
skipped++;
} else {
throw err;
}
}
}
console.log(`Done. Created: ${created}, Skipped (duplicate): ${skipped}`);
console.log('Default password for all demo users: Demo@1234');
}
main()
.catch(err => { console.error(err); process.exit(1); })
.finally(() => prisma.$disconnect());