Initial Push to GIT
This commit is contained in:
242
backend/src/models/db.js
Normal file
242
backend/src/models/db.js
Normal file
@@ -0,0 +1,242 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const DB_PATH = process.env.DB_PATH || '/app/data/teamchat.db';
|
||||
|
||||
let db;
|
||||
|
||||
function getDb() {
|
||||
if (!db) {
|
||||
// Ensure the data directory exists before opening the DB
|
||||
const dir = path.dirname(DB_PATH);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
console.log(`[DB] Created data directory: ${dir}`);
|
||||
}
|
||||
db = new Database(DB_PATH);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
console.log(`[DB] Opened database at ${DB_PATH}`);
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
function initDb() {
|
||||
const db = getDb();
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'member',
|
||||
status TEXT NOT NULL DEFAULT 'active',
|
||||
is_default_admin INTEGER NOT NULL DEFAULT 0,
|
||||
must_change_password INTEGER NOT NULL DEFAULT 1,
|
||||
avatar TEXT,
|
||||
about_me TEXT,
|
||||
display_name TEXT,
|
||||
hide_admin_tag INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'public',
|
||||
owner_id INTEGER,
|
||||
is_default INTEGER NOT NULL DEFAULT 0,
|
||||
is_readonly INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (owner_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS group_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
joined_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(group_id, user_id),
|
||||
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
content TEXT,
|
||||
type TEXT NOT NULL DEFAULT 'text',
|
||||
image_url TEXT,
|
||||
reply_to_id INTEGER,
|
||||
is_deleted INTEGER NOT NULL DEFAULT 0,
|
||||
link_preview TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (reply_to_id) REFERENCES messages(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS reactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
message_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
emoji TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(message_id, user_id, emoji),
|
||||
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
message_id INTEGER,
|
||||
group_id INTEGER,
|
||||
from_user_id INTEGER,
|
||||
is_read INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
expires_at TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
`);
|
||||
|
||||
// Initialize default settings
|
||||
const insertSetting = db.prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)');
|
||||
insertSetting.run('app_name', process.env.APP_NAME || 'TeamChat');
|
||||
insertSetting.run('logo_url', '');
|
||||
insertSetting.run('pw_reset_active', process.env.PW_RESET === 'true' ? 'true' : 'false');
|
||||
insertSetting.run('icon_newchat', '');
|
||||
insertSetting.run('icon_groupinfo', '');
|
||||
insertSetting.run('pwa_icon_192', '');
|
||||
insertSetting.run('pwa_icon_512', '');
|
||||
|
||||
// Migration: add hide_admin_tag if upgrading from older version
|
||||
try {
|
||||
db.exec("ALTER TABLE users ADD COLUMN hide_admin_tag INTEGER NOT NULL DEFAULT 0");
|
||||
console.log('[DB] Migration: added hide_admin_tag column');
|
||||
} catch (e) { /* column already exists */ }
|
||||
|
||||
console.log('[DB] Schema initialized');
|
||||
return db;
|
||||
}
|
||||
|
||||
function seedAdmin() {
|
||||
const db = getDb();
|
||||
|
||||
// Strip any surrounding quotes from env vars (common docker-compose mistake)
|
||||
const adminEmail = (process.env.ADMIN_EMAIL || 'admin@teamchat.local').replace(/^["']|["']$/g, '').trim();
|
||||
const adminName = (process.env.ADMIN_NAME || 'Admin User').replace(/^["']|["']$/g, '').trim();
|
||||
const adminPass = (process.env.ADMIN_PASS || 'Admin@1234').replace(/^["']|["']$/g, '').trim();
|
||||
const pwReset = process.env.PW_RESET === 'true';
|
||||
|
||||
console.log(`[DB] Checking for default admin (${adminEmail})...`);
|
||||
|
||||
const existing = db.prepare('SELECT * FROM users WHERE is_default_admin = 1').get();
|
||||
|
||||
if (!existing) {
|
||||
try {
|
||||
const hash = bcrypt.hashSync(adminPass, 10);
|
||||
const result = db.prepare(`
|
||||
INSERT INTO users (name, email, password, role, status, is_default_admin, must_change_password)
|
||||
VALUES (?, ?, ?, 'admin', 'active', 1, 1)
|
||||
`).run(adminName, adminEmail, hash);
|
||||
|
||||
console.log(`[DB] Default admin created: ${adminEmail} (id=${result.lastInsertRowid})`);
|
||||
|
||||
// Create default TeamChat group
|
||||
const groupResult = db.prepare(`
|
||||
INSERT INTO groups (name, type, is_default, owner_id)
|
||||
VALUES ('TeamChat', 'public', 1, ?)
|
||||
`).run(result.lastInsertRowid);
|
||||
|
||||
// Add admin to default group
|
||||
db.prepare('INSERT OR IGNORE INTO group_members (group_id, user_id) VALUES (?, ?)')
|
||||
.run(groupResult.lastInsertRowid, result.lastInsertRowid);
|
||||
|
||||
console.log('[DB] Default TeamChat group created');
|
||||
seedSupportGroup();
|
||||
} catch (err) {
|
||||
console.error('[DB] ERROR creating default admin:', err.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[DB] Default admin already exists (id=${existing.id})`);
|
||||
|
||||
// Handle PW_RESET
|
||||
if (pwReset) {
|
||||
const hash = bcrypt.hashSync(adminPass, 10);
|
||||
db.prepare(`
|
||||
UPDATE users SET password = ?, must_change_password = 1, updated_at = datetime('now')
|
||||
WHERE is_default_admin = 1
|
||||
`).run(hash);
|
||||
db.prepare("UPDATE settings SET value = 'true', updated_at = datetime('now') WHERE key = 'pw_reset_active'").run();
|
||||
console.log('[DB] Admin password reset via PW_RESET=true');
|
||||
} else {
|
||||
db.prepare("UPDATE settings SET value = 'false', updated_at = datetime('now') WHERE key = 'pw_reset_active'").run();
|
||||
}
|
||||
}
|
||||
|
||||
function addUserToPublicGroups(userId) {
|
||||
const db = getDb();
|
||||
const publicGroups = db.prepare("SELECT id FROM groups WHERE type = 'public'").all();
|
||||
const insert = db.prepare('INSERT OR IGNORE INTO group_members (group_id, user_id) VALUES (?, ?)');
|
||||
for (const g of publicGroups) {
|
||||
insert.run(g.id, userId);
|
||||
}
|
||||
}
|
||||
|
||||
function seedSupportGroup() {
|
||||
const db = getDb();
|
||||
|
||||
// Create the Support group if it doesn't exist
|
||||
const existing = db.prepare("SELECT id FROM groups WHERE name = 'Support' AND type = 'private'").get();
|
||||
if (existing) return existing.id;
|
||||
|
||||
const admin = db.prepare('SELECT id FROM users WHERE is_default_admin = 1').get();
|
||||
if (!admin) return null;
|
||||
|
||||
const result = db.prepare(`
|
||||
INSERT INTO groups (name, type, owner_id, is_default)
|
||||
VALUES ('Support', 'private', ?, 0)
|
||||
`).run(admin.id);
|
||||
|
||||
const groupId = result.lastInsertRowid;
|
||||
|
||||
// Add all current admins to the Support group
|
||||
const admins = db.prepare("SELECT id FROM users WHERE role = 'admin' AND status = 'active'").all();
|
||||
const insert = db.prepare('INSERT OR IGNORE INTO group_members (group_id, user_id) VALUES (?, ?)');
|
||||
for (const a of admins) insert.run(groupId, a.id);
|
||||
|
||||
console.log('[DB] Support group created');
|
||||
return groupId;
|
||||
}
|
||||
|
||||
function getOrCreateSupportGroup() {
|
||||
const db = getDb();
|
||||
const group = db.prepare("SELECT id FROM groups WHERE name = 'Support' AND type = 'private'").get();
|
||||
if (group) return group.id;
|
||||
return seedSupportGroup();
|
||||
}
|
||||
|
||||
module.exports = { getDb, initDb, seedAdmin, seedSupportGroup, getOrCreateSupportGroup, addUserToPublicGroups };
|
||||
Reference in New Issue
Block a user