# Database Seeding ## Overview The database seeding process populates initial data required for the application to function. Seeding runs automatically after migrations in development but must be run manually in production. **Seed Script:** `api/prisma/seed.ts` **Seed Data:** - Default super admin user - Default map settings (Edmonton coordinates) - 6 page blocks for landing page builder - 4 email templates (campaign email, response verification, shift signup confirmation, shift details reminder) --- ## Running Seed ### Development ```bash cd api npm run seed # OR npx prisma db seed ``` ### Production (Docker) ```bash docker compose exec api npx prisma db seed ``` ### CI/CD Seed runs automatically after `prisma migrate deploy` if configured in `package.json`: ```json { "prisma": { "seed": "ts-node prisma/seed.ts" } } ``` --- ## Seed Data Details ### 1. Default Admin User **Email:** `admin@cmlite.org` **Password:** `ChangeMe2025!` **Role:** `SUPER_ADMIN` **Status:** `ACTIVE` **Email Verified:** `true` **Code:** ```typescript const hashedPassword = await bcrypt.hash('ChangeMe2025!', 10); const admin = await prisma.user.upsert({ where: { email: 'admin@cmlite.org' }, update: { password: hashedPassword, emailVerified: true, status: 'ACTIVE', }, create: { email: 'admin@cmlite.org', password: hashedPassword, name: 'Admin', role: UserRole.SUPER_ADMIN, emailVerified: true, }, }); ``` **Security Note:** Change default password immediately after first login! ### 2. Default Map Settings **ID:** `default` (singleton) **Coordinates:** Edmonton, AB (53.5461°N, 113.4938°W) **Zoom:** 11 **Walk Sheet:** Blank titles/footers **Code:** ```typescript await prisma.mapSettings.upsert({ where: { id: 'default' }, update: {}, create: { id: 'default', latitude: 53.5461, longitude: -113.4938, zoom: 11, walkSheetTitle: 'Walk Sheet', walkSheetSubtitle: '', walkSheetFooter: '', }, }); ``` ### 3. Page Blocks (6 blocks) #### Hero Section ```typescript { 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: '#', }, } ``` #### Text Block ```typescript { 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...', }, } ``` #### Features Grid ```typescript { 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...', icon: '' }, { title: 'Advocacy', description: 'Email your representatives...', icon: '' }, { title: 'Volunteer', description: 'Sign up for shifts...', icon: '' }, ], }, } ``` #### Call to Action ```typescript { id: 'default-cta', type: 'cta', label: 'Call to Action', category: 'Actions', sortOrder: 4, // ... (see seed.ts for full schema) } ``` #### Testimonials ```typescript { id: 'default-testimonials', type: 'testimonials', label: 'Testimonials', category: 'Content', sortOrder: 5, // ... (see seed.ts for full schema) } ``` #### Contact Form ```typescript { id: 'default-contact-form', type: 'contact-form', label: 'Contact Form', category: 'Actions', sortOrder: 6, // ... (see seed.ts for full schema) } ``` ### 4. Email Templates (4 templates) #### Campaign Email to Representative **Key:** `campaign-email` **Category:** `INFLUENCE` **Variables:** CAMPAIGN_TITLE, MESSAGE, USER_NAME, USER_EMAIL, POSTAL_CODE, RECIPIENT_NAME, RECIPIENT_LEVEL, ORGANIZATION_NAME, TIMESTAMP **File Locations:** - HTML: `api/src/templates/email/campaign-email.html` - Text: `api/src/templates/email/campaign-email.txt` **Seeding Logic:** ```typescript const templateDef = { key: 'campaign-email', name: 'Campaign Email to Representative', description: 'Email sent when a constituent contacts their elected representative through a campaign', category: EmailTemplateCategory.INFLUENCE, subjectLine: '{{CAMPAIGN_TITLE}} - Message from {{USER_NAME}}', isSystem: true, variables: [ { key: 'CAMPAIGN_TITLE', label: 'Campaign Title', isRequired: true, sampleValue: 'Support Climate Action Bill C-12' }, { key: 'MESSAGE', label: 'Message Body', isRequired: true, sampleValue: 'I urge you to support...' }, // ... 7 more variables ], }; const htmlContent = fs.readFileSync(path.join(templatesDir, `${templateDef.key}.html`), 'utf-8'); const textContent = fs.readFileSync(path.join(templatesDir, `${templateDef.key}.txt`), 'utf-8'); const template = await prisma.emailTemplate.create({ data: { ...templateDef, htmlContent, textContent, createdByUserId: admin.id, variables: { create: templateDef.variables, }, }, }); ``` #### Response Verification **Key:** `response-verification` **Category:** `INFLUENCE` **Variables:** CAMPAIGN_TITLE, RESPONSE_TYPE, RESPONSE_TEXT, SUBMITTER_NAME, SUBMITTED_DATE, VERIFICATION_URL, REPORT_URL, ORGANIZATION_NAME, TIMESTAMP #### Shift Signup Confirmation **Key:** `shift-signup-confirmation` **Category:** `MAP` **Variables:** ORGANIZATION_NAME, USER_NAME, USER_EMAIL, SHIFT_TITLE, SHIFT_DATE, SHIFT_TIME, SHIFT_LOCATION, IS_NEW_USER, TEMP_PASSWORD, LOGIN_URL #### Shift Details Reminder **Key:** `shift-details` **Category:** `MAP` **Variables:** ORGANIZATION_NAME, USER_NAME, SHIFT_TITLE, SHIFT_DATE, SHIFT_START_TIME, SHIFT_END_TIME, SHIFT_LOCATION, SHIFT_DESCRIPTION, CURRENT_VOLUNTEERS, MAX_VOLUNTEERS, SHIFT_STATUS --- ## Seed Script Structure ### Main Function ```typescript async function main() { console.log('Seeding database...'); // 1. Create admin user const admin = await createAdminUser(); // 2. Create map settings await createMapSettings(); // 3. Create page blocks await createPageBlocks(); // 4. Seed email templates await seedEmailTemplates(admin); console.log('Seed complete.'); } ``` ### Upsert Pattern All seed operations use `upsert` to be idempotent: ```typescript await prisma.pageBlock.upsert({ where: { id: block.id }, update: {}, // Don't update if exists create: block, // Create if doesn't exist }); ``` **Benefits:** - Safe to run multiple times - Won't duplicate data - Won't overwrite user changes (empty `update` clause) ### Error Handling ```typescript main() .catch((e) => { console.error('Seed error:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); }); ``` --- ## Customizing Seed Data ### Change Admin Credentials Edit `api/prisma/seed.ts`: ```typescript const hashedPassword = await bcrypt.hash('YourSecurePassword123!', 10); const admin = await prisma.user.upsert({ where: { email: 'your-email@example.com' }, // Change email update: { password: hashedPassword, emailVerified: true, status: 'ACTIVE', }, create: { email: 'your-email@example.com', // Change email password: hashedPassword, name: 'Your Name', // Change name role: UserRole.SUPER_ADMIN, emailVerified: true, }, }); ``` ### Change Map Default Location Edit `api/prisma/seed.ts`: ```typescript await prisma.mapSettings.upsert({ where: { id: 'default' }, update: {}, create: { id: 'default', latitude: 51.0447, // Calgary, AB longitude: -114.0719, zoom: 11, walkSheetTitle: 'Calgary Canvass Walk Sheet', walkSheetSubtitle: 'District Canvassing', walkSheetFooter: 'Thank you for volunteering!', }, }); ``` ### Add Custom Page Blocks ```typescript const customBlocks = [ { id: 'custom-video', type: 'video', label: 'Video Embed', category: 'Media', sortOrder: 7, schema: { videoUrl: { type: 'string', label: 'Video URL' }, caption: { type: 'string', label: 'Caption' }, }, defaults: { videoUrl: 'https://www.youtube.com/embed/...', caption: 'Watch our video', }, }, ]; for (const block of customBlocks) { await prisma.pageBlock.upsert({ where: { id: block.id }, update: {}, create: block, }); } ``` --- ## Verifying Seed Data ### Check Admin User ```bash docker compose exec api npx prisma studio # Navigate to users table, filter by role = "SUPER_ADMIN" ``` ### Check Map Settings ```bash docker compose exec v2-postgres psql -U changemaker_v2 changemaker_v2 -c "SELECT * FROM map_settings;" ``` ### Check Page Blocks ```bash docker compose exec v2-postgres psql -U changemaker_v2 changemaker_v2 -c "SELECT id, type, label FROM page_blocks ORDER BY sort_order;" ``` ### Check Email Templates ```bash docker compose exec v2-postgres psql -U changemaker_v2 changemaker_v2 -c "SELECT key, name, category FROM email_templates;" ``` --- ## Troubleshooting ### Error: "Unique constraint failed on email" **Cause:** Admin user already exists **Solution:** Seed uses `upsert`, so this shouldn't happen. Check seed script for typos. ### Error: "Template files not found" **Cause:** Email template `.html`/`.txt` files missing **Solution:** Ensure `api/src/templates/email/` directory contains: - `campaign-email.html` - `campaign-email.txt` - `response-verification.html` - `response-verification.txt` - `shift-signup-confirmation.html` - `shift-signup-confirmation.txt` - `shift-details.html` - `shift-details.txt` ### Error: "Cannot find module 'bcryptjs'" **Cause:** Dependencies not installed **Solution:** ```bash cd api && npm install ``` ### Seed doesn't run after migration **Cause:** `package.json` missing `prisma.seed` config **Solution:** Add to `api/package.json`: ```json { "prisma": { "seed": "ts-node prisma/seed.ts" } } ``` --- ## Production Seeding ### Initial Deployment ```bash # 1. Deploy migrations docker compose exec api npx prisma migrate deploy # 2. Run seed docker compose exec api npx prisma db seed # 3. Change admin password immediately # Login at https://app.cmlite.org with admin@cmlite.org / ChangeMe2025! # Navigate to /app/profile, update password ``` ### Subsequent Deployments **Don't re-run seed** unless adding new seed data (new page blocks, email templates, etc.). Existing seed data uses `upsert` with empty `update` clause, so it won't overwrite user changes. --- ## Related Documentation - [Database Overview](./index.md) — Complete ER diagram - [Schema Reference](./schema.md) — All model fields - [Migration Workflow](./migrations.md) — Prisma migrations - [Auth Models](./models/auth.md) — User model details - [Settings Models](./models/settings.md) — MapSettings details - [Landing Page Models](./models/pages.md) — PageBlock details - [Email Template Models](./models/email-templates.md) — EmailTemplate details