Tonne of udpatess
This commit is contained in:
@@ -130,6 +130,7 @@ model User {
|
||||
invoices Invoice[] @relation("UserInvoices")
|
||||
payments Payment[] @relation("UserPayments")
|
||||
paymentAudits PaymentAuditLog[] @relation("PaymentAuditUser")
|
||||
orders Order[] @relation("UserOrders")
|
||||
notifications Notification[] @relation("UserNotifications")
|
||||
notificationPreferences NotificationPreferences? @relation("NotificationPreferences")
|
||||
|
||||
@@ -293,6 +294,8 @@ model CampaignEmail {
|
||||
|
||||
@@index([campaignId])
|
||||
@@index([campaignSlug])
|
||||
@@index([userPostalCode])
|
||||
@@index([sentAt])
|
||||
@@map("campaign_emails")
|
||||
}
|
||||
|
||||
@@ -357,6 +360,7 @@ model RepresentativeResponse {
|
||||
|
||||
@@index([campaignId])
|
||||
@@index([campaignSlug])
|
||||
@@index([representativeName])
|
||||
@@map("representative_responses")
|
||||
}
|
||||
|
||||
@@ -779,6 +783,7 @@ model MapSettings {
|
||||
qrCode2Label String?
|
||||
qrCode3Url String?
|
||||
qrCode3Label String?
|
||||
publicMapEnabled Boolean @default(true)
|
||||
createdBy String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -836,6 +841,9 @@ model SiteSettings {
|
||||
enableMap Boolean @default(true)
|
||||
enableNewsletter Boolean @default(true)
|
||||
enableLandingPages Boolean @default(true)
|
||||
enableMediaFeatures Boolean @default(true) @map("enable_media_features")
|
||||
enablePayments Boolean @default(false)
|
||||
enableGalleryAds Boolean @default(false) @map("enable_gallery_ads")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -851,6 +859,7 @@ enum EmailTemplateCategory {
|
||||
INFLUENCE
|
||||
MAP
|
||||
SYSTEM
|
||||
PAYMENT
|
||||
}
|
||||
|
||||
enum EmailTemplateVariableType {
|
||||
@@ -1305,6 +1314,7 @@ enum SubscriptionStatus {
|
||||
grace_period
|
||||
delinquent
|
||||
lifetime
|
||||
cancelled
|
||||
}
|
||||
|
||||
enum InvoiceStatus {
|
||||
@@ -1318,12 +1328,27 @@ enum PaymentStatus {
|
||||
pending
|
||||
succeeded
|
||||
failed
|
||||
refunded
|
||||
}
|
||||
|
||||
enum PaymentMethod {
|
||||
card
|
||||
bank_transfer
|
||||
crypto
|
||||
stripe
|
||||
}
|
||||
|
||||
enum ProductType {
|
||||
DIGITAL
|
||||
EVENT
|
||||
DONATION
|
||||
}
|
||||
|
||||
enum OrderStatus {
|
||||
PENDING
|
||||
COMPLETED
|
||||
FAILED
|
||||
REFUNDED
|
||||
}
|
||||
|
||||
enum NotificationType {
|
||||
@@ -1402,6 +1427,9 @@ model Video {
|
||||
averageWatchTimeSeconds Decimal @default(0) @map("average_watch_time_seconds") @db.Decimal(10, 2)
|
||||
completionRate Decimal @default(0) @map("completion_rate") @db.Decimal(5, 2)
|
||||
|
||||
// Content gating
|
||||
accessLevel String @default("free") @map("access_level") // free|member|premium
|
||||
|
||||
// Ordering
|
||||
position Int? @default(0)
|
||||
|
||||
@@ -2018,17 +2046,28 @@ model Ad {
|
||||
imagePath String? @map("image_path")
|
||||
linkUrl String? @map("link_url")
|
||||
title String?
|
||||
subtitle String? @db.Text
|
||||
ctaText String? @map("cta_text")
|
||||
ctaStyle String? @default("primary") @map("cta_style")
|
||||
bgColor String? @map("bg_color")
|
||||
iconEmoji String? @map("icon_emoji")
|
||||
isSystemAd Boolean @default(false) @map("is_system_ad")
|
||||
frequency Int @default(6)
|
||||
visibility String @default("everyone")
|
||||
isActive Boolean? @default(true) @map("is_active")
|
||||
position Int? @default(0)
|
||||
impressionCount Int? @default(0) @map("impression_count")
|
||||
clickCount Int? @default(0) @map("click_count")
|
||||
startDate DateTime? @map("start_date")
|
||||
endDate DateTime? @map("end_date")
|
||||
productId String? @unique @map("product_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime? @map("updated_at")
|
||||
|
||||
// Relations
|
||||
impressions AdImpression[]
|
||||
clicks AdClick[]
|
||||
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([type], map: "idx_ads_type")
|
||||
@@index([isActive], map: "idx_ads_is_active")
|
||||
@@ -3039,13 +3078,22 @@ model PipelineTemplate {
|
||||
// ============================================================================
|
||||
|
||||
model SubscriptionPlan {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
priceCAD Int @map("price_cad")
|
||||
durationDays Int @map("duration_days")
|
||||
features Json?
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
priceCAD Int @map("price_cad")
|
||||
durationDays Int @map("duration_days")
|
||||
features Json?
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Stripe integration
|
||||
stripeProductId String? @map("stripe_product_id")
|
||||
stripePriceId String? @map("stripe_price_id")
|
||||
stripeYearlyPriceId String? @map("stripe_yearly_price_id")
|
||||
yearlyPriceCAD Int? @map("yearly_price_cad")
|
||||
description String? @db.Text
|
||||
tier Int @default(0)
|
||||
displayOrder Int @default(0) @map("display_order")
|
||||
|
||||
// Relations
|
||||
subscriptions UserSubscription[]
|
||||
@@ -3054,14 +3102,20 @@ model SubscriptionPlan {
|
||||
}
|
||||
|
||||
model UserSubscription {
|
||||
id Int @id @default(autoincrement())
|
||||
userId String @map("user_id")
|
||||
planId Int @map("plan_id")
|
||||
status SubscriptionStatus @default(active)
|
||||
startDate DateTime @map("start_date")
|
||||
endDate DateTime @map("end_date")
|
||||
cancelledAt DateTime? @map("cancelled_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
id Int @id @default(autoincrement())
|
||||
userId String @map("user_id")
|
||||
planId Int @map("plan_id")
|
||||
status SubscriptionStatus @default(active)
|
||||
startDate DateTime @map("start_date")
|
||||
endDate DateTime @map("end_date")
|
||||
cancelledAt DateTime? @map("cancelled_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Stripe integration
|
||||
stripeSubscriptionId String? @unique @map("stripe_subscription_id")
|
||||
stripeCustomerId String? @map("stripe_customer_id")
|
||||
currentPeriodEnd DateTime? @map("current_period_end")
|
||||
cancelAtPeriodEnd Boolean @default(false) @map("cancel_at_period_end")
|
||||
|
||||
// Relations
|
||||
user User @relation("UserSubscriptions", fields: [userId], references: [id])
|
||||
@@ -3085,6 +3139,10 @@ model Invoice {
|
||||
description String?
|
||||
metadata Json?
|
||||
|
||||
// Stripe integration
|
||||
stripeInvoiceId String? @unique @map("stripe_invoice_id")
|
||||
type String @default("subscription") @map("invoice_type") // subscription|product|donation
|
||||
|
||||
// Relations
|
||||
user User @relation("UserInvoices", fields: [userId], references: [id])
|
||||
payments Payment[]
|
||||
@@ -3096,16 +3154,20 @@ model Invoice {
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id Int @id @default(autoincrement())
|
||||
invoiceId Int @map("invoice_id")
|
||||
userId String @map("user_id")
|
||||
amountCAD Int @map("amount_cad")
|
||||
method PaymentMethod
|
||||
status PaymentStatus @default(pending)
|
||||
externalId String? @map("external_id")
|
||||
metadata Json?
|
||||
processedAt DateTime? @map("processed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
id Int @id @default(autoincrement())
|
||||
invoiceId Int @map("invoice_id")
|
||||
userId String @map("user_id")
|
||||
amountCAD Int @map("amount_cad")
|
||||
method PaymentMethod
|
||||
status PaymentStatus @default(pending)
|
||||
externalId String? @map("external_id")
|
||||
metadata Json?
|
||||
processedAt DateTime? @map("processed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
// Stripe integration
|
||||
stripePaymentIntentId String? @unique @map("stripe_payment_intent_id")
|
||||
stripeCheckoutSessionId String? @map("stripe_checkout_session_id")
|
||||
|
||||
// Relations
|
||||
invoice Invoice @relation(fields: [invoiceId], references: [id])
|
||||
@@ -3138,6 +3200,87 @@ model PaymentAuditLog {
|
||||
@@map("payment_audit_log")
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(cuid())
|
||||
slug String @unique
|
||||
title String
|
||||
description String? @db.Text
|
||||
priceCAD Int @map("price_cad")
|
||||
type ProductType
|
||||
stripeProductId String? @map("stripe_product_id")
|
||||
stripePriceId String? @map("stripe_price_id")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
imageUrl String? @map("image_url")
|
||||
downloadUrl String? @map("download_url")
|
||||
metadata Json?
|
||||
maxPurchases Int? @map("max_purchases")
|
||||
purchaseCount Int @default(0) @map("purchase_count")
|
||||
createdByUserId String? @map("created_by_user_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
orders Order[]
|
||||
galleryAd Ad?
|
||||
|
||||
@@index([type], map: "idx_products_type")
|
||||
@@index([isActive], map: "idx_products_active")
|
||||
@@map("products")
|
||||
}
|
||||
|
||||
model Order {
|
||||
id String @id @default(cuid())
|
||||
userId String? @map("user_id")
|
||||
productId String? @map("product_id")
|
||||
amountCAD Int @map("amount_cad")
|
||||
status OrderStatus @default(PENDING)
|
||||
stripeCheckoutSessionId String? @unique @map("stripe_checkout_session_id")
|
||||
stripePaymentIntentId String? @map("stripe_payment_intent_id")
|
||||
type String @default("product") @map("order_type") // product|donation
|
||||
|
||||
// Buyer info (for guests)
|
||||
buyerEmail String @map("buyer_email")
|
||||
buyerName String? @map("buyer_name")
|
||||
|
||||
// Donation-specific
|
||||
donorMessage String? @db.Text @map("donor_message")
|
||||
isAnonymous Boolean @default(false) @map("is_anonymous")
|
||||
|
||||
completedAt DateTime? @map("completed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
// Relations
|
||||
user User? @relation("UserOrders", fields: [userId], references: [id])
|
||||
product Product? @relation(fields: [productId], references: [id])
|
||||
|
||||
@@index([userId], map: "idx_orders_user")
|
||||
@@index([productId], map: "idx_orders_product")
|
||||
@@index([status], map: "idx_orders_status")
|
||||
@@index([type], map: "idx_orders_type")
|
||||
@@map("orders")
|
||||
}
|
||||
|
||||
model PaymentSettings {
|
||||
id String @id @default(cuid())
|
||||
stripeSecretKey String @default("") @map("stripe_secret_key")
|
||||
stripePublishableKey String @default("") @map("stripe_publishable_key")
|
||||
stripeWebhookSecret String @default("") @map("stripe_webhook_secret")
|
||||
defaultCurrency String @default("cad") @map("default_currency")
|
||||
|
||||
// Donation settings
|
||||
enableDonations Boolean @default(true) @map("enable_donations")
|
||||
donationSuggestedAmounts Json @default("[1000, 2500, 5000, 10000]") @map("donation_suggested_amounts")
|
||||
donationMinimum Int @default(500) @map("donation_minimum")
|
||||
donationPageTitle String @default("Support Our Work") @map("donation_page_title")
|
||||
donationPageDescription String? @db.Text @map("donation_page_description")
|
||||
thankYouMessage String @default("Thank you for your support!") @db.Text @map("thank_you_message")
|
||||
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@map("payment_settings")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// NOTIFICATIONS
|
||||
// ============================================================================
|
||||
@@ -3299,3 +3442,20 @@ model VideoScheduleHistory {
|
||||
@@index([scheduledByUserId], map: "idx_video_schedule_history_user")
|
||||
@@map("video_schedule_history")
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DOCS ANALYTICS
|
||||
// ============================================================================
|
||||
|
||||
model DocsPageView {
|
||||
id String @id @default(cuid())
|
||||
path String // e.g. "/docs/getting-started/"
|
||||
referrer String? @db.Text // document.referrer
|
||||
sessionHash String? // anonymous session UUID (sessionStorage)
|
||||
userAgent String? // for device type breakdown
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([path, createdAt])
|
||||
@@map("docs_page_views")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import bcrypt from 'bcryptjs';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { env } from '../src/config/env';
|
||||
import { initEncryption, encrypt } from '../src/utils/crypto';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -77,12 +78,32 @@ async function main() {
|
||||
|
||||
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.
|
||||
// Seed SiteSettings with .env SMTP values (only if no row exists yet)
|
||||
const existingSettings = await prisma.siteSettings.findFirst();
|
||||
if (!existingSettings) {
|
||||
// Initialize encryption so we can encrypt the SMTP password
|
||||
const encryptionKey = env.ENCRYPTION_KEY || env.JWT_ACCESS_SECRET;
|
||||
initEncryption(encryptionKey);
|
||||
|
||||
const isMailhog = env.EMAIL_TEST_MODE === 'true' || env.SMTP_HOST === 'mailhog-changemaker';
|
||||
|
||||
await prisma.siteSettings.create({
|
||||
data: {
|
||||
smtpHost: env.SMTP_HOST,
|
||||
smtpPort: env.SMTP_PORT,
|
||||
smtpUser: env.SMTP_USER,
|
||||
smtpPass: env.SMTP_PASS ? encrypt(env.SMTP_PASS) : '',
|
||||
smtpFromAddress: env.SMTP_FROM,
|
||||
emailFromName: env.SMTP_FROM_NAME,
|
||||
smtpActiveProvider: isMailhog ? 'mailhog' : 'production',
|
||||
emailTestMode: env.EMAIL_TEST_MODE === 'true',
|
||||
testEmailRecipient: env.TEST_EMAIL_RECIPIENT,
|
||||
},
|
||||
});
|
||||
console.log('Created SiteSettings with SMTP config from .env');
|
||||
} else {
|
||||
console.log('SiteSettings already exists, skipping SMTP seeding');
|
||||
}
|
||||
|
||||
// Create default page blocks for landing page builder
|
||||
const defaultBlocks = [
|
||||
@@ -239,6 +260,57 @@ async function main() {
|
||||
viewCount: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'default-donate-button',
|
||||
type: 'donate-button',
|
||||
label: 'Donate Button',
|
||||
category: 'Payments',
|
||||
sortOrder: 9,
|
||||
schema: {
|
||||
buttonText: { type: 'string', label: 'Button Text', default: 'Donate Now' },
|
||||
showAmounts: { type: 'boolean', label: 'Show Suggested Amounts', default: true },
|
||||
heading: { type: 'string', label: 'Heading', default: 'Support Our Cause' },
|
||||
description: { type: 'string', label: 'Description' },
|
||||
},
|
||||
defaults: {
|
||||
buttonText: 'Donate Now',
|
||||
showAmounts: true,
|
||||
heading: 'Support Our Cause',
|
||||
description: 'Your contribution helps us create lasting change in our community.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'default-pricing-table',
|
||||
type: 'pricing-table',
|
||||
label: 'Pricing Table',
|
||||
category: 'Payments',
|
||||
sortOrder: 10,
|
||||
schema: {
|
||||
showYearly: { type: 'boolean', label: 'Show Yearly Toggle', default: true },
|
||||
heading: { type: 'string', label: 'Heading', default: 'Choose Your Plan' },
|
||||
description: { type: 'string', label: 'Description' },
|
||||
},
|
||||
defaults: {
|
||||
showYearly: true,
|
||||
heading: 'Choose Your Plan',
|
||||
description: 'Get access to exclusive content and features.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'default-product-card',
|
||||
type: 'product-card',
|
||||
label: 'Product Card',
|
||||
category: 'Payments',
|
||||
sortOrder: 11,
|
||||
schema: {
|
||||
productSlug: { type: 'string', label: 'Product Slug', required: true },
|
||||
buttonText: { type: 'string', label: 'Button Text', default: 'Buy Now' },
|
||||
},
|
||||
defaults: {
|
||||
productSlug: '',
|
||||
buttonText: 'Buy Now',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const block of defaultBlocks) {
|
||||
@@ -258,6 +330,9 @@ async function main() {
|
||||
console.warn('⚠️ No admin user found - skipping email template seeding');
|
||||
}
|
||||
|
||||
// Seed pre-made gallery ads (all inactive by default — admin enables manually)
|
||||
await seedGalleryAds();
|
||||
|
||||
console.log('Seed complete.');
|
||||
}
|
||||
|
||||
@@ -404,6 +479,58 @@ async function seedEmailTemplates(admin: { id: string; email: string }) {
|
||||
{ key: 'ORGANIZATION_NAME', label: 'Organization Name', description: 'Name of the organization', isRequired: true, isConditional: false, sampleValue: 'Changemaker Lite', sortOrder: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'donation-receipt',
|
||||
name: 'Donation Receipt',
|
||||
description: 'Receipt email sent to donors after a successful donation payment',
|
||||
category: EmailTemplateCategory.PAYMENT,
|
||||
subjectLine: 'Donation Receipt — {{ORGANIZATION_NAME}}',
|
||||
isSystem: true,
|
||||
variables: [
|
||||
{ key: 'RECIPIENT_NAME', label: 'Donor Name', description: 'Name of the donor', isRequired: true, isConditional: false, sampleValue: 'Jane Doe', sortOrder: 0 },
|
||||
{ key: 'AMOUNT', label: 'Amount', description: 'Donation amount formatted with dollar sign', isRequired: true, isConditional: false, sampleValue: '$25.00', sortOrder: 1 },
|
||||
{ key: 'ORDER_ID', label: 'Order ID', description: 'Unique reference ID for the donation', isRequired: true, isConditional: false, sampleValue: 'clxyz123abc', sortOrder: 2 },
|
||||
{ key: 'DONATION_DATE', label: 'Donation Date', description: 'Date the donation was completed', isRequired: true, isConditional: false, sampleValue: 'February 17, 2026', sortOrder: 3 },
|
||||
{ key: 'DONOR_MESSAGE', label: 'Donor Message', description: 'Optional message from the donor', isRequired: false, isConditional: true, sampleValue: 'Keep up the great work!', sortOrder: 4 },
|
||||
{ key: 'IS_ANONYMOUS', label: 'Is Anonymous', description: 'Whether the donation is anonymous', isRequired: false, isConditional: true, sampleValue: 'true', sortOrder: 5 },
|
||||
{ key: 'ORGANIZATION_NAME', label: 'Organization Name', description: 'Name of the organization', isRequired: true, isConditional: false, sampleValue: 'Changemaker Lite', sortOrder: 6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'product-receipt',
|
||||
name: 'Product Purchase Receipt',
|
||||
description: 'Receipt email sent to buyers after a successful product purchase',
|
||||
category: EmailTemplateCategory.PAYMENT,
|
||||
subjectLine: 'Purchase Receipt — {{ORGANIZATION_NAME}}',
|
||||
isSystem: true,
|
||||
variables: [
|
||||
{ key: 'RECIPIENT_NAME', label: 'Buyer Name', description: 'Name of the buyer', isRequired: true, isConditional: false, sampleValue: 'John Smith', sortOrder: 0 },
|
||||
{ key: 'AMOUNT', label: 'Amount', description: 'Purchase amount formatted with dollar sign', isRequired: true, isConditional: false, sampleValue: '$49.99', sortOrder: 1 },
|
||||
{ key: 'ORDER_ID', label: 'Order ID', description: 'Unique order reference ID', isRequired: true, isConditional: false, sampleValue: 'clxyz456def', sortOrder: 2 },
|
||||
{ key: 'PRODUCT_TITLE', label: 'Product Title', description: 'Title of the purchased product', isRequired: true, isConditional: false, sampleValue: 'Community Toolkit', sortOrder: 3 },
|
||||
{ key: 'PRODUCT_TYPE', label: 'Product Type', description: 'Type of product (DIGITAL, EVENT, DONATION)', isRequired: true, isConditional: false, sampleValue: 'DIGITAL', sortOrder: 4 },
|
||||
{ key: 'PURCHASE_DATE', label: 'Purchase Date', description: 'Date of purchase', isRequired: true, isConditional: false, sampleValue: 'February 17, 2026', sortOrder: 5 },
|
||||
{ key: 'ORGANIZATION_NAME', label: 'Organization Name', description: 'Name of the organization', isRequired: true, isConditional: false, sampleValue: 'Changemaker Lite', sortOrder: 6 },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'subscription-welcome',
|
||||
name: 'Subscription Welcome',
|
||||
description: 'Welcome email sent when a user subscribes to a plan',
|
||||
category: EmailTemplateCategory.PAYMENT,
|
||||
subjectLine: 'Welcome to {{PLAN_NAME}} — {{ORGANIZATION_NAME}}',
|
||||
isSystem: true,
|
||||
variables: [
|
||||
{ key: 'RECIPIENT_NAME', label: 'User Name', description: 'Name of the subscriber', isRequired: true, isConditional: false, sampleValue: 'Jane Doe', sortOrder: 0 },
|
||||
{ key: 'PLAN_NAME', label: 'Plan Name', description: 'Name of the subscription plan', isRequired: true, isConditional: false, sampleValue: 'Pro Plan', sortOrder: 1 },
|
||||
{ key: 'AMOUNT', label: 'Amount', description: 'Subscription price formatted with dollar sign', isRequired: true, isConditional: false, sampleValue: '$9.99', sortOrder: 2 },
|
||||
{ key: 'FREQUENCY', label: 'Billing Frequency', description: 'How often the subscription renews', isRequired: true, isConditional: false, sampleValue: 'per month', sortOrder: 3 },
|
||||
{ key: 'RENEWAL_DATE', label: 'Renewal Date', description: 'Next renewal date', isRequired: true, isConditional: false, sampleValue: 'March 17, 2026', sortOrder: 4 },
|
||||
{ key: 'SUBSCRIPTION_ID', label: 'Subscription ID', description: 'Stripe subscription ID', isRequired: true, isConditional: false, sampleValue: 'sub_1234567890', sortOrder: 5 },
|
||||
{ key: 'LOGIN_URL', label: 'Login URL', description: 'URL to log in to the platform', isRequired: true, isConditional: false, sampleValue: 'https://app.cmlite.org/login', sortOrder: 6 },
|
||||
{ key: 'ORGANIZATION_NAME', label: 'Organization Name', description: 'Name of the organization', isRequired: true, isConditional: false, sampleValue: 'Changemaker Lite', sortOrder: 7 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
let seededCount = 0;
|
||||
@@ -476,6 +603,131 @@ async function seedEmailTemplates(admin: { id: string; email: string }) {
|
||||
console.log(`Email templates seeded: ${seededCount} created, ${skippedCount} skipped`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed pre-made gallery ads
|
||||
*/
|
||||
async function seedGalleryAds() {
|
||||
console.log('Seeding gallery ads...');
|
||||
|
||||
const defaultAds = [
|
||||
{
|
||||
type: 'system',
|
||||
variant: 'standard',
|
||||
title: 'Join the Community',
|
||||
subtitle: 'Create a free account to upvote, comment, and save favorites',
|
||||
ctaText: 'Sign Up Free',
|
||||
ctaStyle: 'primary',
|
||||
linkUrl: '/login',
|
||||
visibility: 'anonymous',
|
||||
frequency: 8,
|
||||
position: 1,
|
||||
iconEmoji: null,
|
||||
bgColor: null,
|
||||
imagePath: null,
|
||||
},
|
||||
{
|
||||
type: 'payment_subscribe',
|
||||
variant: 'highlight',
|
||||
title: 'Unlock Premium Content',
|
||||
subtitle: 'Subscribe for exclusive videos, early access, and more',
|
||||
ctaText: 'View Plans',
|
||||
ctaStyle: 'primary',
|
||||
linkUrl: '/pricing',
|
||||
visibility: 'non_subscriber',
|
||||
frequency: 12,
|
||||
position: 2,
|
||||
iconEmoji: null,
|
||||
bgColor: null,
|
||||
imagePath: null,
|
||||
},
|
||||
{
|
||||
type: 'payment_donate',
|
||||
variant: 'standard',
|
||||
title: 'Support Our Mission',
|
||||
subtitle: 'Your donation helps us create more content',
|
||||
ctaText: 'Donate Now',
|
||||
ctaStyle: 'primary',
|
||||
linkUrl: '/donate',
|
||||
visibility: 'everyone',
|
||||
frequency: 18,
|
||||
position: 3,
|
||||
iconEmoji: null,
|
||||
bgColor: null,
|
||||
imagePath: null,
|
||||
},
|
||||
{
|
||||
type: 'payment_shop',
|
||||
variant: 'standard',
|
||||
title: 'Browse the Shop',
|
||||
subtitle: 'Exclusive merchandise, downloads, and event tickets',
|
||||
ctaText: 'Shop Now',
|
||||
ctaStyle: 'primary',
|
||||
linkUrl: '/shop',
|
||||
visibility: 'everyone',
|
||||
frequency: 24,
|
||||
position: 4,
|
||||
iconEmoji: null,
|
||||
bgColor: null,
|
||||
imagePath: null,
|
||||
},
|
||||
{
|
||||
type: 'system',
|
||||
variant: 'standard',
|
||||
title: 'Take Action',
|
||||
subtitle: 'Join an advocacy campaign and make your voice heard',
|
||||
ctaText: 'View Campaigns',
|
||||
ctaStyle: 'primary',
|
||||
linkUrl: '/campaigns',
|
||||
visibility: 'everyone',
|
||||
frequency: 18,
|
||||
position: 5,
|
||||
iconEmoji: null,
|
||||
bgColor: null,
|
||||
imagePath: null,
|
||||
},
|
||||
{
|
||||
type: 'system',
|
||||
variant: 'standard',
|
||||
title: 'Volunteer With Us',
|
||||
subtitle: 'Sign up for a shift and help make a difference',
|
||||
ctaText: 'See Shifts',
|
||||
ctaStyle: 'primary',
|
||||
linkUrl: '/shifts',
|
||||
visibility: 'everyone',
|
||||
frequency: 24,
|
||||
position: 6,
|
||||
iconEmoji: null,
|
||||
bgColor: null,
|
||||
imagePath: null,
|
||||
},
|
||||
];
|
||||
|
||||
let seeded = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const ad of defaultAds) {
|
||||
const existing = await prisma.ad.findFirst({
|
||||
where: { type: ad.type, title: ad.title },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
await prisma.ad.create({
|
||||
data: {
|
||||
...ad,
|
||||
isSystemAd: true,
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
seeded++;
|
||||
}
|
||||
|
||||
console.log(`Gallery ads seeded: ${seeded} created, ${skipped} skipped`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error('Seed error:', e);
|
||||
|
||||
Reference in New Issue
Block a user