feat: integrate Electron for desktop application support

- Added Electron entry point in `electron/main.js` to run the Express server in-process and open the main application window.
- Updated `package.json` to include Electron dependencies and scripts for building and running the application.
- Refactored server startup logic into `server/start.js` for better modularity and to support both CLI and Electron usage.
- Implemented environment variable handling for database and image paths to accommodate Electron's packaging.
- Created a script `server/scripts/promote-morphix.js` to merge admin and morphix accounts into a single user.
- Adjusted image root path resolution in `server/media/images.js` to allow for environment variable overrides.
- Cleaned up `server/index.js` to delegate server initialization to the new `startServer` function.
This commit is contained in:
Marco Mooren
2026-05-11 18:55:02 +02:00
parent b86dcfbb8d
commit f6cdfd975c
9 changed files with 7887 additions and 2726 deletions

View File

@@ -0,0 +1,63 @@
// One-off: collapse admin + morphix into a single account named "morphix".
// Run with: npx electron server/scripts/promote-morphix.js
//
// Strategy: if both users exist, delete morphix (which has no favorites/history),
// then rename admin -> morphix and reset its password. If only one exists,
// just rename/ensure morphix with the new password.
import Database from 'better-sqlite3';
import bcrypt from 'bcryptjs';
import { resolve } from 'node:path';
import { app } from 'electron';
const NEW_NAME = 'morphix';
const NEW_PASSWORD = '234Tgb999!';
const dbPath = process.env.DB_PATH || resolve(process.cwd(), 'data', 'db', 'oradio.sqlite');
console.log('[promote] db:', dbPath);
const db = new Database(dbPath);
db.pragma('foreign_keys = ON');
const admin = db.prepare('SELECT id FROM users WHERE username = ?').get('admin');
const morphix = db.prepare('SELECT id FROM users WHERE username = ?').get('morphix');
const hash = bcrypt.hashSync(NEW_PASSWORD, 10);
db.transaction(() => {
if (admin && morphix) {
// Drop the empty morphix so the username is free, then rename admin.
db.prepare('DELETE FROM users WHERE id = ?').run(morphix.id);
db.prepare('UPDATE users SET username = ?, password_hash = ?, role = ? WHERE id = ?')
.run(NEW_NAME, hash, 'admin', admin.id);
// Ensure profile display name is sane.
db.prepare(`INSERT INTO profiles (user_id, display_name) VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET display_name = excluded.display_name`)
.run(admin.id, NEW_NAME);
console.log(`[promote] merged: morphix (id ${morphix.id}) deleted, admin (id ${admin.id}) renamed to morphix`);
} else if (admin && !morphix) {
db.prepare('UPDATE users SET username = ?, password_hash = ?, role = ? WHERE id = ?')
.run(NEW_NAME, hash, 'admin', admin.id);
db.prepare(`INSERT INTO profiles (user_id, display_name) VALUES (?, ?)
ON CONFLICT(user_id) DO UPDATE SET display_name = excluded.display_name`)
.run(admin.id, NEW_NAME);
console.log(`[promote] renamed admin -> morphix (id ${admin.id})`);
} else if (!admin && morphix) {
db.prepare('UPDATE users SET password_hash = ?, role = ? WHERE id = ?')
.run(hash, 'admin', morphix.id);
console.log(`[promote] morphix already exists (id ${morphix.id}); password reset`);
} else {
const info = db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)')
.run(NEW_NAME, hash, 'admin');
db.prepare('INSERT INTO profiles (user_id, display_name) VALUES (?, ?)')
.run(info.lastInsertRowid, NEW_NAME);
console.log(`[promote] created morphix (id ${info.lastInsertRowid})`);
}
// Invalidate any cached sessions so existing logins don't end up tied
// to a now-deleted user id.
db.prepare('DELETE FROM sessions').run();
})();
db.close();
console.log('[promote] done. Sessions cleared — log in again as morphix / ' + NEW_PASSWORD);
app.quit();