Spaces:
Sleeping
Sleeping
File size: 9,563 Bytes
57a889c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | import Database from 'better-sqlite3';
import crypto from 'crypto';
// bcrypt cost factor for the seeded admin password β kept in sync with authService.
const BCRYPT_COST = 12;
// Seeds run at startup before the DB admin panel can be used, so only env vars
// are checked here. The granular password_login/password_registration DB toggles
// are only relevant after the first user exists; at that point seeds have already
// finished and skip via the userCount > 0 guard above.
function isOidcOnlyConfigured(): boolean {
if (process.env.OIDC_ONLY?.toLowerCase() !== 'true') return false;
return !!(process.env.OIDC_ISSUER && process.env.OIDC_CLIENT_ID);
}
function seedAdminAccount(db: Database.Database): void {
try {
const userCount = (db.prepare('SELECT COUNT(*) as count FROM users').get() as { count: number }).count;
if (userCount > 0) return;
if (isOidcOnlyConfigured()) {
console.log('');
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('β TREK β OIDC-Only Mode β');
console.log('β First SSO login will become admin. β');
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('');
return;
}
const bcrypt = require('bcryptjs');
const env_admin_email = process.env.ADMIN_EMAIL;
const env_admin_pw = process.env.ADMIN_PASSWORD;
let password;
let email;
if (env_admin_email && env_admin_pw) {
password = env_admin_pw;
email = env_admin_email;
} else {
password = crypto.randomBytes(12).toString('base64url');
email = 'admin@trek.local';
}
const hash = bcrypt.hashSync(password, BCRYPT_COST);
const username = 'admin';
db.prepare('INSERT INTO users (username, email, password_hash, role, must_change_password) VALUES (?, ?, ?, ?, 1)').run(username, email, hash, 'admin');
console.log('');
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('β TREK β First Run: Admin Account Created β');
console.log('β βββββββββββββββββββββββββββββββββββββββββββββββ£');
console.log(`β Email: ${email.padEnd(33)}β`);
console.log(`β Password: ${password.padEnd(33)}β`);
console.log('ββββββββββββββββββββββββββββββββββββββββββββββββ');
console.log('');
} catch (err: unknown) {
console.error('[ERROR] Error seeding admin account:', err instanceof Error ? err.message : err);
}
}
function seedCategories(db: Database.Database): void {
try {
const existingCats = db.prepare('SELECT COUNT(*) as count FROM categories').get() as { count: number };
if (existingCats.count === 0) {
const defaultCategories = [
{ name: 'Hotel', color: '#3b82f6', icon: 'π¨' },
{ name: 'Restaurant', color: '#ef4444', icon: 'π½οΈ' },
{ name: 'Attraction', color: '#8b5cf6', icon: 'ποΈ' },
{ name: 'Shopping', color: '#f59e0b', icon: 'ποΈ' },
{ name: 'Transport', color: '#6b7280', icon: 'π' },
{ name: 'Activity', color: '#10b981', icon: 'π―' },
{ name: 'Bar/Cafe', color: '#f97316', icon: 'β' },
{ name: 'Beach', color: '#06b6d4', icon: 'ποΈ' },
{ name: 'Nature', color: '#84cc16', icon: 'πΏ' },
{ name: 'Other', color: '#6366f1', icon: 'π' },
];
const insertCat = db.prepare('INSERT INTO categories (name, color, icon) VALUES (?, ?, ?)');
for (const cat of defaultCategories) insertCat.run(cat.name, cat.color, cat.icon);
console.log('Default categories seeded');
}
} catch (err: unknown) {
console.error('Error seeding categories:', err instanceof Error ? err.message : err);
}
}
function seedAddons(db: Database.Database): void {
try {
const defaultAddons = [
{ id: 'packing', name: 'Lists', description: 'Packing lists and to-do tasks for your trips', type: 'trip', icon: 'ListChecks', enabled: 1, sort_order: 0 },
{ id: 'budget', name: 'Costs', description: 'Track and split trip expenses', type: 'trip', icon: 'Wallet', enabled: 1, sort_order: 1 },
{ id: 'documents', name: 'Documents', description: 'Store and manage travel documents', type: 'trip', icon: 'FileText', enabled: 1, sort_order: 2 },
{ id: 'vacay', name: 'Vacay', description: 'Personal vacation day planner with calendar view', type: 'global', icon: 'CalendarDays', enabled: 1, sort_order: 10 },
{ id: 'atlas', name: 'Atlas', description: 'World map of your visited countries with travel stats', type: 'global', icon: 'Globe', enabled: 1, sort_order: 11 },
{ id: 'mcp', name: 'MCP', description: 'Model Context Protocol for AI assistant integration', type: 'integration', icon: 'Terminal', enabled: 0, sort_order: 12 },
{ id: 'naver_list_import', name: 'Naver List Import', description: 'Import places from shared Naver Maps lists', type: 'trip', icon: 'Link2', enabled: 1, sort_order: 13 },
{ id: 'collab', name: 'Collab', description: 'Notes, polls, and live chat for trip collaboration', type: 'trip', icon: 'Users', enabled: 1, sort_order: 6 },
{ id: 'journey', name: 'Journey', description: 'Trip tracking & travel journal β check-ins, photos, daily stories', type: 'global', icon: 'Compass', enabled: 0, sort_order: 35 },
{ id: 'airtrail', name: 'AirTrail', description: 'Sync flights from your self-hosted AirTrail instance', type: 'integration', icon: 'Plane', enabled: 0, sort_order: 14 },
];
const insertAddon = db.prepare('INSERT OR IGNORE INTO addons (id, name, description, type, icon, enabled, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?)');
for (const a of defaultAddons) insertAddon.run(a.id, a.name, a.description, a.type, a.icon, a.enabled, a.sort_order);
const providerRows = [
{
id: 'immich',
name: 'Immich',
description: 'Immich photo provider',
icon: 'Image',
enabled: 0,
sort_order: 0,
},
{
id: 'synologyphotos',
name: 'Synology Photos',
description: 'Synology Photos integration with separate account settings',
icon: 'Image',
enabled: 0,
sort_order: 1,
},
];
const insertProvider = db.prepare('INSERT OR IGNORE INTO photo_providers (id, name, description, icon, enabled, sort_order) VALUES (?, ?, ?, ?, ?, ?)');
for (const p of providerRows) insertProvider.run(p.id, p.name, p.description, p.icon, p.enabled, p.sort_order);
const providerFields = [
{ provider_id: 'immich', field_key: 'immich_url', label: 'providerUrl', input_type: 'url', placeholder: 'https://immich.example.com', hint: null, required: 1, secret: 0, settings_key: 'immich_url', payload_key: 'immich_url', sort_order: 0 },
{ provider_id: 'immich', field_key: 'immich_api_key', label: 'providerApiKey', input_type: 'password', placeholder: 'API Key', hint: null, required: 1, secret: 1, settings_key: null, payload_key: 'immich_api_key', sort_order: 1 },
{ provider_id: 'synologyphotos', field_key: 'synology_url', label: 'providerUrl', input_type: 'url', placeholder: 'https://synology.example.com/photo', hint: 'providerUrlHintSynology', required: 1, secret: 0, settings_key: 'synology_url', payload_key: 'synology_url', sort_order: 0 },
{ provider_id: 'synologyphotos', field_key: 'synology_username', label: 'providerUsername', input_type: 'text', placeholder: 'Username', hint: null, required: 1, secret: 0, settings_key: 'synology_username', payload_key: 'synology_username', sort_order: 1 },
{ provider_id: 'synologyphotos', field_key: 'synology_password', label: 'providerPassword', input_type: 'password', placeholder: 'Password', hint: null, required: 1, secret: 1, settings_key: null, payload_key: 'synology_password', sort_order: 2 },
{ provider_id: 'synologyphotos', field_key: 'synology_otp', label: 'providerOTP', input_type: 'text', placeholder: '123456', hint: null, required: 0, secret: 0, settings_key: null, payload_key: 'synology_otp', sort_order: 3 },
{ provider_id: 'synologyphotos', field_key: 'synology_skip_ssl', label: 'skipSSLVerification', input_type: 'checkbox', placeholder: null, hint: null, required: 0, secret: 0, settings_key: 'synology_skip_ssl', payload_key: 'synology_skip_ssl', sort_order: 4 },
];
const insertProviderField = db.prepare('INSERT OR IGNORE INTO photo_provider_fields (provider_id, field_key, label, input_type, placeholder, hint, required, secret, settings_key, payload_key, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
for (const f of providerFields) {
insertProviderField.run(f.provider_id, f.field_key, f.label, f.input_type, f.placeholder, f.hint, f.required, f.secret, f.settings_key, f.payload_key, f.sort_order);
}
console.log('Default addons seeded');
} catch (err: unknown) {
console.error('Error seeding addons:', err instanceof Error ? err.message : err);
}
}
function runSeeds(db: Database.Database): void {
seedAdminAccount(db);
seedCategories(db);
seedAddons(db);
}
export { runSeeds };
|