bunker-admin a77306fac2 Initial v2 commit: complete rebuild with unified API + React admin
Phase 1-14 complete:
- Unified Express.js API (TypeScript, Prisma ORM, PostgreSQL 16)
- React Admin GUI (Vite + Ant Design + Zustand)
- JWT auth with refresh tokens
- Influence: Campaigns, Representatives, Responses, Email Queue
- Map: Locations, Cuts, Shifts, Canvassing System
- NAR data import infrastructure (2025 format)
- Listmonk newsletter integration
- Landing page builder (GrapesJS)
- MkDocs + Code Server integration
- Volunteer portal with GPS tracking
- Monitoring stack (Prometheus, Grafana, Alertmanager)
- Pangolin tunnel integration

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 10:05:04 -07:00

182 lines
5.2 KiB
TypeScript

import { PrismaClient, UserRole } from '@prisma/client';
import bcrypt from 'bcryptjs';
const prisma = new PrismaClient();
async function main() {
console.log('Seeding database...');
// Create default super admin
const hashedPassword = await bcrypt.hash('changeme', 10);
const admin = await prisma.user.upsert({
where: { email: 'admin@cmlite.org' },
update: {},
create: {
email: 'admin@cmlite.org',
password: hashedPassword,
name: 'Admin',
role: UserRole.SUPER_ADMIN,
emailVerified: true,
},
});
console.log(`Created admin user: ${admin.email}`);
// Create default map settings
await prisma.mapSettings.upsert({
where: { id: 'default' },
update: {},
create: {
id: 'default',
latitude: 53.5461,
longitude: -113.4938,
zoom: 11,
walkSheetTitle: 'Walk Sheet',
walkSheetSubtitle: '',
walkSheetFooter: '',
},
});
console.log('Created default map settings');
// Phase 3: v1 data migration will go here
// - Export NocoDB data via API
// - Import influence_users + login tables → unified users
// - Deduplicate by email
// - Hash plaintext passwords
// - Import campaigns, locations, shifts, cuts, etc.
// Create default page blocks for landing page builder
const defaultBlocks = [
{
id: 'default-hero',
type: 'hero',
label: 'Hero Section',
category: 'Headers',
sortOrder: 1,
schema: {
title: { type: 'string', label: 'Title' },
subtitle: { type: 'string', label: 'Subtitle' },
backgroundImage: { type: 'string', label: 'Background Image URL' },
ctaText: { type: 'string', label: 'Button Text' },
ctaUrl: { type: 'string', label: 'Button URL' },
},
defaults: {
title: 'Welcome to Our Campaign',
subtitle: 'Join us in making a difference in your community.',
backgroundImage: '',
ctaText: 'Get Involved',
ctaUrl: '#',
},
},
{
id: 'default-text',
type: 'text',
label: 'Text Block',
category: 'Content',
sortOrder: 2,
schema: {
heading: { type: 'string', label: 'Heading' },
body: { type: 'text', label: 'Body Text' },
},
defaults: {
heading: 'About Us',
body: 'Tell your story here. Explain your mission, values, and what drives your campaign forward.',
},
},
{
id: 'default-features',
type: 'features',
label: 'Features Grid',
category: 'Content',
sortOrder: 3,
schema: {
features: { type: 'array', label: 'Features', items: { title: 'string', description: 'string', icon: 'string' } },
},
defaults: {
features: [
{ title: 'Community Action', description: 'Organize local events and initiatives.', icon: '' },
{ title: 'Advocacy', description: 'Email your representatives directly.', icon: '' },
{ title: 'Volunteer', description: 'Sign up for shifts and make a difference.', icon: '' },
],
},
},
{
id: 'default-cta',
type: 'cta',
label: 'Call to Action',
category: 'Actions',
sortOrder: 4,
schema: {
heading: { type: 'string', label: 'Heading' },
description: { type: 'string', label: 'Description' },
buttonText: { type: 'string', label: 'Button Text' },
buttonUrl: { type: 'string', label: 'Button URL' },
},
defaults: {
heading: 'Ready to Take Action?',
description: 'Join thousands of community members making their voices heard.',
buttonText: 'Join Now',
buttonUrl: '#',
},
},
{
id: 'default-testimonials',
type: 'testimonials',
label: 'Testimonials',
category: 'Content',
sortOrder: 5,
schema: {
quotes: { type: 'array', label: 'Quotes', items: { text: 'string', author: 'string', role: 'string' } },
},
defaults: {
quotes: [
{ text: 'This platform made it so easy to contact my representatives.', author: 'Jane D.', role: 'Community Member' },
{ text: 'I signed up for a volunteer shift and it changed my perspective.', author: 'Mark S.', role: 'Volunteer' },
],
},
},
{
id: 'default-contact-form',
type: 'contact-form',
label: 'Contact Form',
category: 'Actions',
sortOrder: 6,
schema: {
heading: { type: 'string', label: 'Heading' },
fields: { type: 'array', label: 'Fields', items: { name: 'string', type: 'string', required: 'boolean' } },
},
defaults: {
heading: 'Get in Touch',
fields: [
{ name: 'Name', type: 'text', required: true },
{ name: 'Email', type: 'email', required: true },
{ name: 'Message', type: 'textarea', required: true },
],
},
},
];
for (const block of defaultBlocks) {
await prisma.pageBlock.upsert({
where: { id: block.id },
update: {},
create: block,
});
}
console.log(`Created ${defaultBlocks.length} default page blocks`);
console.log('Seed complete.');
}
main()
.catch((e) => {
console.error('Seed error:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});