feat(auth): migrate user store from SQLite to PostgreSQL
Build & Push Docker Images / test-backend (push) Successful in 44s
Build & Push Docker Images / test-frontend (push) Successful in 53s
Build & Push Docker Images / build-backend (push) Failing after 50s
Build & Push Docker Images / build-frontend (push) Successful in 54s
Build & Push Docker Images / test-backend (push) Successful in 44s
Build & Push Docker Images / test-frontend (push) Successful in 53s
Build & Push Docker Images / build-backend (push) Failing after 50s
Build & Push Docker Images / build-frontend (push) Successful in 54s
Users were wiped on container recreation because they shared the backend_data volume with ephemeral pharmacy/medicine data. Postgres gets its own named volume (postgres_data) that survives deploys. - Add postgres:16-alpine service + postgres_data volume to compose - Add pg + connect-pg-simple deps - userDbGet/userDbRun helpers: PG when PG_URL set, SQLite fallback - Sessions use connect-pg-simple when pgPool available - create-admin.js: PG-aware, SQLite fallback for local dev - push_subscriptions.user_id: plain INTEGER (cross-DB FK dropped) - Tests unchanged — no PG_URL in test env = SQLite fallback Set PG_PASSWORD in server .env before next deploy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+76
-67
@@ -3,86 +3,95 @@ import { promisify } from 'util';
|
||||
import bcrypt from 'bcrypt';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import pg from 'pg';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const dbPath = path.join(__dirname, 'database.sqlite');
|
||||
const db = new sqlite3.Database(dbPath);
|
||||
|
||||
// Custom wrapper to get lastID from db.run
|
||||
function dbRun(sql, params = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function(err) {
|
||||
if (err) reject(err);
|
||||
else resolve({ lastID: this.lastID, changes: this.changes });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const dbGet = promisify(db.get.bind(db));
|
||||
|
||||
// Initialize users table
|
||||
async function initDatabase() {
|
||||
try {
|
||||
await dbRun(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('Database table initialized');
|
||||
} catch (error) {
|
||||
console.error('Error initializing database:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const PG_URL = process.env.PG_URL;
|
||||
|
||||
async function createAdmin() {
|
||||
try {
|
||||
// Initialize database tables first
|
||||
await initDatabase();
|
||||
const username = process.env.ADMIN_USERNAME || 'admin';
|
||||
const password = process.env.ADMIN_PASSWORD || 'admin123';
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
|
||||
// Default admin credentials
|
||||
const username = process.env.ADMIN_USERNAME || 'admin';
|
||||
const password = process.env.ADMIN_PASSWORD || 'admin123';
|
||||
if (PG_URL) {
|
||||
const { Pool } = pg;
|
||||
const pool = new Pool({ connectionString: PG_URL });
|
||||
try {
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
is_admin INTEGER NOT NULL DEFAULT 0,
|
||||
address TEXT,
|
||||
latitude DOUBLE PRECISION,
|
||||
longitude DOUBLE PRECISION,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
// Check if admin already exists
|
||||
const existing = await dbGet(
|
||||
'SELECT * FROM users WHERE username = ?',
|
||||
[username]
|
||||
);
|
||||
const existing = await pool.query('SELECT id FROM users WHERE username = $1', [username]);
|
||||
if (existing.rows.length > 0) {
|
||||
console.log(`Admin user '${username}' already exists.`);
|
||||
console.log('To reset, delete the user first and re-run.');
|
||||
await pool.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
console.log(`Admin user '${username}' already exists.`);
|
||||
console.log('To change the password, delete the user first and run this script again.');
|
||||
db.close();
|
||||
return;
|
||||
await pool.query(
|
||||
'INSERT INTO users (username, password_hash, is_admin) VALUES ($1, $2, 1)',
|
||||
[username, passwordHash]
|
||||
);
|
||||
console.log(`Admin user '${username}' created in PostgreSQL.`);
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
} else {
|
||||
const dbPath = process.env.DATABASE_PATH || path.join(__dirname, 'database.sqlite');
|
||||
const db = new sqlite3.Database(dbPath);
|
||||
const dbRun = (sql, params = []) =>
|
||||
new Promise((resolve, reject) =>
|
||||
db.run(sql, params, function (err) { err ? reject(err) : resolve({ lastID: this.lastID }); })
|
||||
);
|
||||
const dbGet = promisify(db.get.bind(db));
|
||||
|
||||
// Hash password
|
||||
const saltRounds = 10;
|
||||
const passwordHash = await bcrypt.hash(password, saltRounds);
|
||||
try {
|
||||
await dbRun(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
try { await dbRun('ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0'); } catch {}
|
||||
|
||||
// Create admin user
|
||||
await dbRun(
|
||||
'INSERT INTO users (username, password_hash) VALUES (?, ?)',
|
||||
[username, passwordHash]
|
||||
);
|
||||
const existing = await dbGet('SELECT id FROM users WHERE username = ?', [username]);
|
||||
if (existing) {
|
||||
console.log(`Admin user '${username}' already exists.`);
|
||||
console.log('To reset, delete the user first and re-run.');
|
||||
db.close();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ Admin user created successfully!');
|
||||
console.log(`Username: ${username}`);
|
||||
console.log(`Password: ${password}`);
|
||||
console.log('\n⚠️ IMPORTANT: Change the default password after first login!');
|
||||
console.log(' You can set ADMIN_USERNAME and ADMIN_PASSWORD environment variables to customize.');
|
||||
} catch (error) {
|
||||
console.error('Error creating admin user:', error);
|
||||
} finally {
|
||||
db.close();
|
||||
await dbRun(
|
||||
'INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, 1)',
|
||||
[username, passwordHash]
|
||||
);
|
||||
console.log(`Admin user '${username}' created in SQLite.`);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Username: ${username}`);
|
||||
console.log(`Password: ${password}`);
|
||||
console.log('\nIMPORTANT: Change the default password after first login!');
|
||||
}
|
||||
|
||||
createAdmin();
|
||||
|
||||
createAdmin().catch((err) => {
|
||||
console.error('Error creating admin user:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user