setup.ts raw
1 import { writeFileSync, existsSync, mkdirSync } from 'fs';
2 import { join, dirname } from 'path';
3 import { getDb, closeDb } from './lib/db';
4 import { seedProducts } from './lib/products';
5 import bcrypt from 'bcryptjs';
6 import crypto from 'crypto';
7
8 function env(key: string, def = ''): string {
9 return process.env[key] || def;
10 }
11
12 async function main() {
13 console.log('\n=== Zlattea - Setup ===\n');
14
15 const dbPath = env('DB_PATH', join(process.cwd(), 'data', 'shop.db'));
16 const dataDir = dirname(dbPath);
17 if (!existsSync(dataDir)) mkdirSync(dataDir, { recursive: true });
18
19 const siteUrl = env('SITE_URL', 'http://localhost:3000');
20 const adminEmail = env('ADMIN_EMAIL', 'admin@zlattea.com');
21 const adminPass = env('ADMIN_PASSWORD');
22 const port = env('PORT', '3000');
23 const apiPort = env('API_PORT', String(Number(port) + 1));
24 const bind = env('HOST', '127.0.0.1');
25 const nostrPubkey = env('NOSTR_PUBKEY');
26 const lud16 = env('LUD16_ADDRESS', 'shop@zlattea.com');
27
28 if (!adminPass) {
29 console.error('ADMIN_PASSWORD must be set explicitly - refusing to create an admin with a default password.');
30 process.exit(1);
31 }
32
33 const envFile = `# site
34 SITE_URL=${siteUrl}
35 PORT=${port}
36 API_PORT=${apiPort}
37 HOST=${bind}
38 DB_PATH=${dbPath}
39
40 # lightning
41 LUD16_ADDRESS=${lud16}
42 BTC_BGN_RATE=${env('BTC_BGN_RATE')}
43
44 # stripe (card payments - google pay, apple pay come free with stripe)
45 STRIPE_SECRET_KEY=${env('STRIPE_SECRET_KEY')}
46 STRIPE_PUBLISHABLE_KEY=${env('STRIPE_PUBLISHABLE_KEY')}
47 STRIPE_WEBHOOK_SECRET=${env('STRIPE_WEBHOOK_SECRET')}
48
49 # btcpayserver (onchain bitcoin)
50 BTCPAY_URL=${env('BTCPAY_URL')}
51 BTCPAY_API_KEY=${env('BTCPAY_API_KEY')}
52 BTCPAY_STORE_ID=${env('BTCPAY_STORE_ID')}
53 BTCPAY_WEBHOOK_SECRET=${env('BTCPAY_WEBHOOK_SECRET')}
54
55 # courier api credentials (optional - leave empty if not needed)
56 SPEEDY_USERNAME=${env('SPEEDY_USERNAME')}
57 SPEEDY_PASSWORD=${env('SPEEDY_PASSWORD')}
58
59 # email (order confirmations; leave empty to disable)
60 SMTP_HOST=${env('SMTP_HOST')}
61 SMTP_PORT=${env('SMTP_PORT', '587')}
62 SMTP_USER=${env('SMTP_USER')}
63 SMTP_PASS=${env('SMTP_PASS')}
64 MAIL_FROM=${env('MAIL_FROM', 'shop@zlattea.com')}
65 `;
66 writeFileSync(join(process.cwd(), '.env'), envFile);
67
68 console.log('setting up database...');
69 process.env.DB_PATH = dbPath;
70 const db = getDb(); // applies schema + migration
71 const seeded = seedProducts();
72 console.log(`products: ${seeded}`);
73
74 const existing = db.query('SELECT id FROM users WHERE email = ?').get(adminEmail) as any;
75 if (existing) {
76 console.log('admin user already exists.');
77 } else {
78 const hash = bcrypt.hashSync(adminPass, 10);
79 db.run('INSERT INTO users (id, email, password_hash, name, is_admin) VALUES (?, ?, ?, ?, 1)',
80 [crypto.randomUUID(), adminEmail, hash, 'Admin']);
81 console.log('admin user created.');
82 }
83 if (nostrPubkey) {
84 db.run('UPDATE users SET nostr_pubkey = ? WHERE email = ?', [nostrPubkey, adminEmail]);
85 }
86
87 // default shipping config
88 db.run("INSERT OR IGNORE INTO content (key, value) VALUES ('shipping_rate_per_kg', '500')");
89 db.run("INSERT OR IGNORE INTO content (key, value) VALUES ('free_shipping_threshold', '5000')");
90 console.log('default shipping: 5.00 EUR/kg, free over 50.00 EUR');
91 closeDb();
92
93 console.log('\nsetup complete. run: bun run build && bun run start\n');
94 }
95
96 main().catch(e => { console.error(e); process.exit(1); });
97