import 'dotenv/config'; import { Client, Databases, ID, Permission, Role } from "node-appwrite"; /** * EmailSorter Database Bootstrap Script v2 * Creates all required collections for the full EmailSorter app */ const requiredEnv = [ "APPWRITE_ENDPOINT", "APPWRITE_PROJECT_ID", "APPWRITE_API_KEY", ]; for (const k of requiredEnv) { if (!process.env[k]) { console.error(`Missing env var: ${k}`); process.exit(1); } } const client = new Client() .setEndpoint(process.env.APPWRITE_ENDPOINT) .setProject(process.env.APPWRITE_PROJECT_ID) .setKey(process.env.APPWRITE_API_KEY); const db = new Databases(client); const DB_ID = process.env.APPWRITE_DATABASE_ID || 'emailsorter'; const DB_NAME = 'EmailSorter'; // Helper: create database if not exists async function ensureDatabase() { try { await db.get(DB_ID); console.log("✓ Database exists:", DB_ID); } catch { await db.create(DB_ID, DB_NAME); console.log("✓ Database created:", DB_ID); } } // Helper: create collection if not exists async function ensureCollection(collectionId, name, permissions = []) { try { await db.getCollection(DB_ID, collectionId); console.log(`✓ Collection exists: ${collectionId}`); } catch { await db.createCollection(DB_ID, collectionId, name, permissions, true); console.log(`✓ Collection created: ${collectionId}`); } } // Helper: create attribute if not exists async function ensureAttribute(collectionId, key, createFn) { const attributes = await db.listAttributes(DB_ID, collectionId); const exists = attributes.attributes?.some(a => a.key === key); if (exists) { console.log(` - Attribute exists: ${collectionId}.${key}`); return; } await createFn(); console.log(` + Attribute created: ${collectionId}.${key}`); // Wait for attribute to be ready await new Promise(resolve => setTimeout(resolve, 1000)); } // Permission templates const PERM_PUBLIC_READ = [Permission.read(Role.any())]; const PERM_AUTHENTICATED = [ Permission.read(Role.users()), Permission.create(Role.users()), ]; const PERM_SERVER_ONLY = []; async function setupCollections() { // ==================== Products ==================== await ensureCollection('products', 'Products', PERM_PUBLIC_READ); await ensureAttribute('products', 'slug', () => db.createStringAttribute(DB_ID, 'products', 'slug', 128, true)); await ensureAttribute('products', 'title', () => db.createStringAttribute(DB_ID, 'products', 'title', 256, true)); await ensureAttribute('products', 'description', () => db.createStringAttribute(DB_ID, 'products', 'description', 4096, false)); await ensureAttribute('products', 'priceCents', () => db.createIntegerAttribute(DB_ID, 'products', 'priceCents', true, 0, 999999999)); await ensureAttribute('products', 'currency', () => db.createStringAttribute(DB_ID, 'products', 'currency', 8, true)); await ensureAttribute('products', 'isActive', () => db.createBooleanAttribute(DB_ID, 'products', 'isActive', true)); // ==================== Questions ==================== await ensureCollection('questions', 'Questions', PERM_PUBLIC_READ); await ensureAttribute('questions', 'productId', () => db.createStringAttribute(DB_ID, 'questions', 'productId', 64, true)); await ensureAttribute('questions', 'key', () => db.createStringAttribute(DB_ID, 'questions', 'key', 64, true)); await ensureAttribute('questions', 'label', () => db.createStringAttribute(DB_ID, 'questions', 'label', 256, true)); await ensureAttribute('questions', 'helpText', () => db.createStringAttribute(DB_ID, 'questions', 'helpText', 1024, false)); await ensureAttribute('questions', 'type', () => db.createStringAttribute(DB_ID, 'questions', 'type', 32, true)); await ensureAttribute('questions', 'required', () => db.createBooleanAttribute(DB_ID, 'questions', 'required', true)); await ensureAttribute('questions', 'step', () => db.createIntegerAttribute(DB_ID, 'questions', 'step', true, 1, 9999)); await ensureAttribute('questions', 'order', () => db.createIntegerAttribute(DB_ID, 'questions', 'order', true, 1, 999999)); await ensureAttribute('questions', 'optionsJson', () => db.createStringAttribute(DB_ID, 'questions', 'optionsJson', 8192, false)); await ensureAttribute('questions', 'isActive', () => db.createBooleanAttribute(DB_ID, 'questions', 'isActive', true)); // ==================== Submissions ==================== await ensureCollection('submissions', 'Submissions', PERM_AUTHENTICATED); await ensureAttribute('submissions', 'productId', () => db.createStringAttribute(DB_ID, 'submissions', 'productId', 64, true)); await ensureAttribute('submissions', 'status', () => db.createStringAttribute(DB_ID, 'submissions', 'status', 32, true)); await ensureAttribute('submissions', 'customerEmail', () => db.createEmailAttribute(DB_ID, 'submissions', 'customerEmail', false)); await ensureAttribute('submissions', 'customerName', () => db.createStringAttribute(DB_ID, 'submissions', 'customerName', 256, false)); await ensureAttribute('submissions', 'finalSummaryJson', () => db.createStringAttribute(DB_ID, 'submissions', 'finalSummaryJson', 16384, false)); await ensureAttribute('submissions', 'priceCents', () => db.createIntegerAttribute(DB_ID, 'submissions', 'priceCents', true, 0, 999999999)); await ensureAttribute('submissions', 'currency', () => db.createStringAttribute(DB_ID, 'submissions', 'currency', 8, true)); // ==================== Answers ==================== await ensureCollection('answers', 'Answers', PERM_AUTHENTICATED); await ensureAttribute('answers', 'submissionId', () => db.createStringAttribute(DB_ID, 'answers', 'submissionId', 64, true)); await ensureAttribute('answers', 'answersJson', () => db.createStringAttribute(DB_ID, 'answers', 'answersJson', 16384, true)); // ==================== Orders ==================== await ensureCollection('orders', 'Orders', PERM_SERVER_ONLY); await ensureAttribute('orders', 'submissionId', () => db.createStringAttribute(DB_ID, 'orders', 'submissionId', 64, true)); await ensureAttribute('orders', 'orderDataJson', () => db.createStringAttribute(DB_ID, 'orders', 'orderDataJson', 16384, true)); // ==================== Email Accounts ==================== await ensureCollection('email_accounts', 'Email Accounts', PERM_AUTHENTICATED); await ensureAttribute('email_accounts', 'userId', () => db.createStringAttribute(DB_ID, 'email_accounts', 'userId', 64, true)); await ensureAttribute('email_accounts', 'provider', () => db.createStringAttribute(DB_ID, 'email_accounts', 'provider', 32, true)); await ensureAttribute('email_accounts', 'email', () => db.createEmailAttribute(DB_ID, 'email_accounts', 'email', true)); await ensureAttribute('email_accounts', 'accessToken', () => db.createStringAttribute(DB_ID, 'email_accounts', 'accessToken', 4096, false)); await ensureAttribute('email_accounts', 'refreshToken', () => db.createStringAttribute(DB_ID, 'email_accounts', 'refreshToken', 4096, false)); await ensureAttribute('email_accounts', 'expiresAt', () => db.createIntegerAttribute(DB_ID, 'email_accounts', 'expiresAt', false)); await ensureAttribute('email_accounts', 'isActive', () => db.createBooleanAttribute(DB_ID, 'email_accounts', 'isActive', true)); await ensureAttribute('email_accounts', 'lastSync', () => db.createDatetimeAttribute(DB_ID, 'email_accounts', 'lastSync', false)); // ==================== Email Stats ==================== await ensureCollection('email_stats', 'Email Stats', PERM_AUTHENTICATED); await ensureAttribute('email_stats', 'userId', () => db.createStringAttribute(DB_ID, 'email_stats', 'userId', 64, true)); await ensureAttribute('email_stats', 'totalSorted', () => db.createIntegerAttribute(DB_ID, 'email_stats', 'totalSorted', true, 0)); await ensureAttribute('email_stats', 'todaySorted', () => db.createIntegerAttribute(DB_ID, 'email_stats', 'todaySorted', true, 0)); await ensureAttribute('email_stats', 'weekSorted', () => db.createIntegerAttribute(DB_ID, 'email_stats', 'weekSorted', true, 0)); await ensureAttribute('email_stats', 'categoriesJson', () => db.createStringAttribute(DB_ID, 'email_stats', 'categoriesJson', 4096, false)); await ensureAttribute('email_stats', 'timeSavedMinutes', () => db.createIntegerAttribute(DB_ID, 'email_stats', 'timeSavedMinutes', true, 0)); // ==================== Subscriptions ==================== await ensureCollection('subscriptions', 'Subscriptions', PERM_AUTHENTICATED); await ensureAttribute('subscriptions', 'userId', () => db.createStringAttribute(DB_ID, 'subscriptions', 'userId', 64, true)); await ensureAttribute('subscriptions', 'stripeCustomerId', () => db.createStringAttribute(DB_ID, 'subscriptions', 'stripeCustomerId', 128, false)); await ensureAttribute('subscriptions', 'stripeSubscriptionId', () => db.createStringAttribute(DB_ID, 'subscriptions', 'stripeSubscriptionId', 128, false)); await ensureAttribute('subscriptions', 'plan', () => db.createStringAttribute(DB_ID, 'subscriptions', 'plan', 32, true)); await ensureAttribute('subscriptions', 'status', () => db.createStringAttribute(DB_ID, 'subscriptions', 'status', 32, true)); await ensureAttribute('subscriptions', 'currentPeriodEnd', () => db.createDatetimeAttribute(DB_ID, 'subscriptions', 'currentPeriodEnd', false)); await ensureAttribute('subscriptions', 'cancelAtPeriodEnd', () => db.createBooleanAttribute(DB_ID, 'subscriptions', 'cancelAtPeriodEnd', false)); // ==================== User Preferences ==================== await ensureCollection('user_preferences', 'User Preferences', PERM_AUTHENTICATED); await ensureAttribute('user_preferences', 'userId', () => db.createStringAttribute(DB_ID, 'user_preferences', 'userId', 64, true)); await ensureAttribute('user_preferences', 'preferencesJson', () => db.createStringAttribute(DB_ID, 'user_preferences', 'preferencesJson', 16384, false)); } async function main() { console.log('\n========================================'); console.log(' EmailSorter Database Bootstrap v2'); console.log('========================================\n'); await ensureDatabase(); console.log('\n--- Setting up collections ---\n'); await setupCollections(); console.log('\n========================================'); console.log(' ✓ Bootstrap complete!'); console.log(` Database ID: ${DB_ID}`); console.log('========================================\n'); console.log('Add this to your .env file:'); console.log(`APPWRITE_DATABASE_ID=${DB_ID}\n`); } main().catch((e) => { console.error('Bootstrap failed:', e); process.exit(1); });