const Database = require('better-sqlite3'); const path = require('path'); const bcrypt = require('bcrypt'); const DB_PATH = path.join(__dirname, 'marketing.db'); const db = new Database(DB_PATH); // Enable WAL mode and foreign keys db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); function initialize() { // Create tables db.exec(` CREATE TABLE IF NOT EXISTS team_members ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT, role TEXT, avatar_url TEXT, brands TEXT DEFAULT '[]', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS brands ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, priority INTEGER DEFAULT 2, color TEXT, icon TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, brand_id INTEGER REFERENCES brands(id), assigned_to INTEGER REFERENCES team_members(id), status TEXT DEFAULT 'draft', platform TEXT, content_type TEXT, scheduled_date DATETIME, published_date DATETIME, notes TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS assets ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, original_name TEXT, mime_type TEXT, size INTEGER, tags TEXT DEFAULT '[]', brand_id INTEGER REFERENCES brands(id), campaign_id INTEGER REFERENCES campaigns(id), uploaded_by INTEGER REFERENCES team_members(id), folder TEXT DEFAULT 'general', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS campaigns ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, brand_id INTEGER REFERENCES brands(id), start_date DATE NOT NULL, end_date DATE NOT NULL, status TEXT DEFAULT 'planning', color TEXT, budget REAL, goals TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS projects ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT, brand_id INTEGER REFERENCES brands(id), owner_id INTEGER REFERENCES team_members(id), status TEXT DEFAULT 'active', priority TEXT DEFAULT 'medium', due_date DATE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, project_id INTEGER REFERENCES projects(id), assigned_to INTEGER REFERENCES team_members(id), created_by INTEGER REFERENCES team_members(id), status TEXT DEFAULT 'todo', priority TEXT DEFAULT 'medium', due_date DATE, is_personal BOOLEAN DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, completed_at DATETIME ); `); // Budget entries table — tracks money received db.exec(` CREATE TABLE IF NOT EXISTS budget_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT NOT NULL, amount REAL NOT NULL, source TEXT, campaign_id INTEGER REFERENCES campaigns(id), category TEXT DEFAULT 'marketing', date_received DATE NOT NULL, notes TEXT DEFAULT '', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); // Users table for authentication db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'contributor', avatar TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); // Campaign tracks table db.exec(` CREATE TABLE IF NOT EXISTS campaign_tracks ( id INTEGER PRIMARY KEY AUTOINCREMENT, campaign_id INTEGER NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE, name TEXT, type TEXT NOT NULL DEFAULT 'organic_social', platform TEXT, budget_allocated REAL DEFAULT 0, budget_spent REAL DEFAULT 0, revenue REAL DEFAULT 0, impressions INTEGER DEFAULT 0, clicks INTEGER DEFAULT 0, conversions INTEGER DEFAULT 0, notes TEXT DEFAULT '', status TEXT DEFAULT 'planned', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); // ─── Ownership columns (link to users table) ─── const addOwnership = (table, column) => { const cols = db.prepare(`PRAGMA table_info(${table})`).all().map(c => c.name); if (!cols.includes(column)) { db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} INTEGER REFERENCES users(id)`); console.log(`✅ Added ${column} column to ${table}`); } }; addOwnership('posts', 'created_by_user_id'); addOwnership('tasks', 'created_by_user_id'); addOwnership('campaigns', 'created_by_user_id'); addOwnership('projects', 'created_by_user_id'); // Add phone column to team_members if missing const teamMemberCols = db.prepare("PRAGMA table_info(team_members)").all().map(c => c.name); if (!teamMemberCols.includes('phone')) { db.exec("ALTER TABLE team_members ADD COLUMN phone TEXT"); console.log('✅ Added phone column to team_members'); } // Migrations — add columns if they don't exist const campaignCols = db.prepare("PRAGMA table_info(campaigns)").all().map(c => c.name); if (!campaignCols.includes('platforms')) { db.exec("ALTER TABLE campaigns ADD COLUMN platforms TEXT DEFAULT '[]'"); console.log('✅ Added platforms column to campaigns'); } // Campaign performance tracking columns if (!campaignCols.includes('budget_spent')) { db.exec("ALTER TABLE campaigns ADD COLUMN budget_spent REAL DEFAULT 0"); console.log('✅ Added budget_spent column to campaigns'); } if (!campaignCols.includes('revenue')) { db.exec("ALTER TABLE campaigns ADD COLUMN revenue REAL DEFAULT 0"); console.log('✅ Added revenue column to campaigns'); } if (!campaignCols.includes('impressions')) { db.exec("ALTER TABLE campaigns ADD COLUMN impressions INTEGER DEFAULT 0"); console.log('✅ Added impressions column to campaigns'); } if (!campaignCols.includes('clicks')) { db.exec("ALTER TABLE campaigns ADD COLUMN clicks INTEGER DEFAULT 0"); console.log('✅ Added clicks column to campaigns'); } if (!campaignCols.includes('conversions')) { db.exec("ALTER TABLE campaigns ADD COLUMN conversions INTEGER DEFAULT 0"); console.log('✅ Added conversions column to campaigns'); } if (!campaignCols.includes('cost_per_click')) { db.exec("ALTER TABLE campaigns ADD COLUMN cost_per_click REAL DEFAULT 0"); console.log('✅ Added cost_per_click column to campaigns'); } if (!campaignCols.includes('notes')) { db.exec("ALTER TABLE campaigns ADD COLUMN notes TEXT DEFAULT ''"); console.log('✅ Added notes column to campaigns'); } // Add track_id to posts const postCols = db.prepare("PRAGMA table_info(posts)").all().map(c => c.name); if (!postCols.includes('track_id')) { db.exec("ALTER TABLE posts ADD COLUMN track_id INTEGER REFERENCES campaign_tracks(id)"); console.log('✅ Added track_id column to posts'); } if (!postCols.includes('campaign_id')) { db.exec("ALTER TABLE posts ADD COLUMN campaign_id INTEGER REFERENCES campaigns(id)"); console.log('✅ Added campaign_id column to posts'); } if (!postCols.includes('platforms')) { // Add platforms column, migrate existing platform values db.exec("ALTER TABLE posts ADD COLUMN platforms TEXT DEFAULT '[]'"); // Migrate: copy single platform value into platforms JSON array const rows = db.prepare("SELECT id, platform FROM posts WHERE platform IS NOT NULL AND platform != ''").all(); const migrate = db.prepare("UPDATE posts SET platforms = ? WHERE id = ?"); for (const row of rows) { migrate.run(JSON.stringify([row.platform]), row.id); } console.log(`✅ Added platforms column to posts, migrated ${rows.length} rows`); } // Add campaign_id to assets const assetCols = db.prepare("PRAGMA table_info(assets)").all().map(c => c.name); if (!assetCols.includes('campaign_id')) { db.exec("ALTER TABLE assets ADD COLUMN campaign_id INTEGER REFERENCES campaigns(id)"); console.log('✅ Added campaign_id column to assets'); } // ─── Link users to team_members ─── const userCols = db.prepare("PRAGMA table_info(users)").all().map(c => c.name); if (!userCols.includes('team_member_id')) { db.exec("ALTER TABLE users ADD COLUMN team_member_id INTEGER REFERENCES team_members(id)"); console.log('✅ Added team_member_id column to users'); } // ─── Post attachments table ─── db.exec(` CREATE TABLE IF NOT EXISTS post_attachments ( id INTEGER PRIMARY KEY AUTOINCREMENT, post_id INTEGER NOT NULL REFERENCES posts(id) ON DELETE CASCADE, filename TEXT NOT NULL, original_name TEXT, mime_type TEXT, size INTEGER, url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); `); // ─── Publication links column on posts ─── if (!postCols.includes('publication_links')) { db.exec("ALTER TABLE posts ADD COLUMN publication_links TEXT DEFAULT '[]'"); console.log('✅ Added publication_links column to posts'); } // ─── Merge team_members into users ─── if (!userCols.includes('team_role')) { db.exec("ALTER TABLE users ADD COLUMN team_role TEXT"); console.log('✅ Added team_role column to users'); } if (!userCols.includes('brands')) { db.exec("ALTER TABLE users ADD COLUMN brands TEXT DEFAULT '[]'"); console.log('✅ Added brands column to users'); } if (!userCols.includes('phone')) { db.exec("ALTER TABLE users ADD COLUMN phone TEXT"); console.log('✅ Added phone column to users'); } if (!userCols.includes('tutorial_completed')) { db.exec("ALTER TABLE users ADD COLUMN tutorial_completed INTEGER DEFAULT 0"); console.log('✅ Added tutorial_completed column to users'); } // Migrate team_members to users (one-time migration) const teamMembers = db.prepare('SELECT * FROM team_members').all(); const defaultPasswordHash = bcrypt.hashSync('changeme123', 10); for (const tm of teamMembers) { // Skip team_member id=9 (Fahed) - he's already user id=1 if (tm.id === 9) { // Just update his team_role and brands db.prepare('UPDATE users SET team_role = ?, brands = ?, team_member_id = ? WHERE id = 1') .run(tm.role, tm.brands, tm.id); continue; } // Check if user already exists with this team_member_id const existingUser = db.prepare('SELECT id FROM users WHERE team_member_id = ?').get(tm.id); if (existingUser) { // User exists, just update team_role and brands db.prepare('UPDATE users SET team_role = ?, brands = ?, phone = ? WHERE id = ?') .run(tm.role, tm.brands, tm.phone || null, existingUser.id); } else { // Create new user for this team member db.prepare(` INSERT INTO users (name, email, password_hash, role, team_role, brands, phone, team_member_id) VALUES (?, ?, ?, 'contributor', ?, ?, ?, ?) `).run( tm.name, tm.email, defaultPasswordHash, tm.role, tm.brands, tm.phone || null, tm.id ); console.log(`✅ Created user account for team member: ${tm.name}`); } } // Seed data only if tables are empty const memberCount = db.prepare('SELECT COUNT(*) as count FROM team_members').get().count; if (memberCount === 0) { seedData(); } // Seed default superadmin if no users exist const userCount = db.prepare('SELECT COUNT(*) as count FROM users').get().count; if (userCount === 0) { seedDefaultUser(); } } function seedData() { const allBrands = JSON.stringify([ 'Samaya Investment', 'Hira Cultural District', 'Holy Quran Museum', 'Al-Safiya Museum', 'Hayhala', 'Jabal Thawr', 'Coffee Chain', 'Taibah Gifts' ]); const someBrands = JSON.stringify([ 'Samaya Investment', 'Hira Cultural District', 'Holy Quran Museum', 'Al-Safiya Museum' ]); const mostAccounts = JSON.stringify([ 'Samaya Investment', 'Hira Cultural District', 'Holy Quran Museum', 'Al-Safiya Museum', 'Hayhala', 'Jabal Thawr', 'Coffee Chain' ]); const religiousExhibitions = JSON.stringify([ 'Holy Quran Museum', 'Al-Safiya Museum', 'Jabal Thawr' ]); const insertMember = db.prepare(` INSERT INTO team_members (name, email, role, brands) VALUES (?, ?, ?, ?) `); const members = [ ['Dr. Muhammad Al-Sayed', 'muhammad.alsayed@samaya.sa', 'approver', allBrands], ['Dr. Fahd Al-Thumairi', 'fahd.thumairi@samaya.sa', 'approver', someBrands], ['Fahda Abdul Aziz', 'fahda@samaya.sa', 'publisher', mostAccounts], ['Sara Al-Zahrani', 'sara@samaya.sa', 'content_creator', JSON.stringify(['Samaya Investment', 'Hira Cultural District', 'Coffee Chain'])], ['Noura', 'noura@samaya.sa', 'content_creator', JSON.stringify(['Samaya Investment', 'Hira Cultural District', 'Hayhala', 'Taibah Gifts'])], ['Saeed Ghanem', 'saeed@samaya.sa', 'content_creator', religiousExhibitions], ['Anas Mater', 'anas@samaya.sa', 'producer', JSON.stringify(['Samaya Investment', 'Hira Cultural District'])], ['Muhammad Nu\'man', 'numan@samaya.sa', 'manager', JSON.stringify(['Google Maps'])], ['Fahed', 'fahed@samaya.sa', 'manager', allBrands], ]; const insertMembers = db.transaction(() => { for (const m of members) { insertMember.run(...m); } }); insertMembers(); // Seed brands const insertBrand = db.prepare(` INSERT INTO brands (name, priority, color, icon) VALUES (?, ?, ?, ?) `); const brands = [ ['Samaya Investment', 1, '#1E3A5F', '🏢'], ['Hira Cultural District', 1, '#8B4513', '🏛️'], ['Holy Quran Museum', 1, '#2E7D32', '📖'], ['Al-Safiya Museum', 1, '#6A1B9A', '🏺'], ['Hayhala', 1, '#C62828', '🎭'], ['Jabal Thawr', 1, '#4E342E', '⛰️'], ['Coffee Chain', 2, '#795548', '☕'], ['Taibah Gifts', 3, '#E65100', '🎁'], ]; const insertBrands = db.transaction(() => { for (const b of brands) { insertBrand.run(...b); } }); insertBrands(); console.log('✅ Database seeded with team members and brands'); } function seedDefaultUser() { const passwordHash = bcrypt.hashSync('admin123', 10); const insertUser = db.prepare(` INSERT INTO users (name, email, password_hash, role) VALUES (?, ?, ?, ?) `); insertUser.run('Fahed Muhaidi', 'f.mahidi@samayainvest.com', passwordHash, 'superadmin'); console.log('✅ Default superadmin created (email: f.mahidi@samayainvest.com, password: admin123)'); } module.exports = { db, initialize };