Files
Emailsorter/server/bootstrap-v2.mjs
ANDJ 6da8ce1cbd huhuih
hzgjuigik
2026-01-27 21:06:48 +01:00

278 lines
14 KiB
JavaScript

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));
// ==================== Onboarding State ====================
await ensureCollection('onboarding_state', 'Onboarding State', PERM_AUTHENTICATED);
await ensureAttribute('onboarding_state', 'userId', () =>
db.createStringAttribute(DB_ID, 'onboarding_state', 'userId', 64, true));
await ensureAttribute('onboarding_state', 'onboarding_step', () =>
db.createStringAttribute(DB_ID, 'onboarding_state', 'onboarding_step', 32, true));
await ensureAttribute('onboarding_state', 'completed_steps_json', () =>
db.createStringAttribute(DB_ID, 'onboarding_state', 'completed_steps_json', 1024, false));
await ensureAttribute('onboarding_state', 'first_value_seen_at', () =>
db.createDatetimeAttribute(DB_ID, 'onboarding_state', 'first_value_seen_at', false));
await ensureAttribute('onboarding_state', 'skipped_at', () =>
db.createDatetimeAttribute(DB_ID, 'onboarding_state', 'skipped_at', false));
await ensureAttribute('onboarding_state', 'last_updated', () =>
db.createDatetimeAttribute(DB_ID, 'onboarding_state', 'last_updated', false));
// ==================== Email Usage ====================
await ensureCollection('email_usage', 'Email Usage', PERM_AUTHENTICATED);
await ensureAttribute('email_usage', 'userId', () =>
db.createStringAttribute(DB_ID, 'email_usage', 'userId', 64, true));
await ensureAttribute('email_usage', 'month', () =>
db.createStringAttribute(DB_ID, 'email_usage', 'month', 16, true)); // "2026-01"
await ensureAttribute('email_usage', 'emailsProcessed', () =>
db.createIntegerAttribute(DB_ID, 'email_usage', 'emailsProcessed', true, 0));
await ensureAttribute('email_usage', 'lastReset', () =>
db.createDatetimeAttribute(DB_ID, 'email_usage', 'lastReset', false));
// ==================== Referrals ====================
await ensureCollection('referrals', 'Referrals', PERM_AUTHENTICATED);
await ensureAttribute('referrals', 'userId', () =>
db.createStringAttribute(DB_ID, 'referrals', 'userId', 64, true));
await ensureAttribute('referrals', 'referralCode', () =>
db.createStringAttribute(DB_ID, 'referrals', 'referralCode', 32, true));
await ensureAttribute('referrals', 'referredBy', () =>
db.createStringAttribute(DB_ID, 'referrals', 'referredBy', 64, false));
await ensureAttribute('referrals', 'referralCount', () =>
db.createIntegerAttribute(DB_ID, 'referrals', 'referralCount', true, 0));
await ensureAttribute('referrals', 'createdAt', () =>
db.createDatetimeAttribute(DB_ID, 'referrals', 'createdAt', false));
// ==================== Analytics Events ====================
await ensureCollection('analytics_events', 'Analytics Events', PERM_PUBLIC_READ);
await ensureAttribute('analytics_events', 'userId', () =>
db.createStringAttribute(DB_ID, 'analytics_events', 'userId', 64, false));
await ensureAttribute('analytics_events', 'eventType', () =>
db.createStringAttribute(DB_ID, 'analytics_events', 'eventType', 64, true));
await ensureAttribute('analytics_events', 'metadataJson', () =>
db.createStringAttribute(DB_ID, 'analytics_events', 'metadataJson', 4096, false));
await ensureAttribute('analytics_events', 'timestamp', () =>
db.createDatetimeAttribute(DB_ID, 'analytics_events', 'timestamp', false));
await ensureAttribute('analytics_events', 'sessionId', () =>
db.createStringAttribute(DB_ID, 'analytics_events', 'sessionId', 64, 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);
});