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>
182 lines
5.2 KiB
TypeScript
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();
|
|
});
|