Spaces:
Sleeping
Sleeping
File size: 5,624 Bytes
2dddd1f ac25f89 2dddd1f ac25f89 2dddd1f ac25f89 2dddd1f ac25f89 2dddd1f | 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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | import { createClient, Client } from '@libsql/client';
import path from 'path';
import crypto from 'crypto';
import dotenv from 'dotenv';
dotenv.config();
// Compatibility wrapper to make LibSQL behave like the previous sqlite-wrapper
export interface Database {
get: (sql: string, params?: any[]) => Promise<any>;
all: (sql: string, params?: any[]) => Promise<any[]>;
run: (sql: string, params?: any[]) => Promise<{ lastID: number | bigint }>;
exec: (sql: string) => Promise<void>;
}
let dbInstance: Database | null = null;
export async function getDb(): Promise<Database> {
if (dbInstance) return dbInstance;
const url = process.env.DATABASE_URL || `file:${path.resolve(__dirname, '../database.sqlite')}`;
const authToken = process.env.DATABASE_AUTH_TOKEN;
const client = createClient({
url,
authToken,
});
const sanitizeParams = (params: any[]): any[] => {
return params.map(p => p === undefined ? null : p);
};
const sanitizeResult = (row: any) => {
if (!row) return row;
const newRow = { ...row };
for (const [key, value] of Object.entries(newRow)) {
if (typeof value === 'bigint') {
newRow[key] = Number(value);
}
}
return newRow;
};
const db: Database = {
get: async (sql: string, params: any[] = []) => {
const res = await client.execute({ sql, args: sanitizeParams(params) });
return res.rows[0] ? sanitizeResult(res.rows[0]) : undefined;
},
all: async (sql: string, params: any[] = []) => {
const res = await client.execute({ sql, args: sanitizeParams(params) });
return res.rows.map(row => sanitizeResult(row));
},
run: async (sql: string, params: any[] = []) => {
const res = await client.execute({ sql, args: sanitizeParams(params) });
return { lastID: Number(res.lastInsertRowid || 0) };
},
exec: async (sql: string) => {
await client.batch(sql.split(';').filter(s => s.trim()), 'write');
}
};
dbInstance = db;
// Initialize tables
await db.exec(`
CREATE TABLE IF NOT EXISTS wallets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
currency TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
amount REAL NOT NULL,
currency TEXT NOT NULL,
wallet_id INTEGER,
to_wallet_id INTEGER,
category TEXT,
note TEXT,
date TEXT NOT NULL,
country_id TEXT,
FOREIGN KEY(wallet_id) REFERENCES wallets(id),
FOREIGN KEY(to_wallet_id) REFERENCES wallets(id)
);
CREATE TABLE IF NOT EXISTS exchanges (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_amount REAL NOT NULL,
from_currency TEXT NOT NULL,
to_amount REAL NOT NULL,
to_currency TEXT NOT NULL,
rate REAL NOT NULL,
date TEXT NOT NULL,
note TEXT,
from_wallet_id INTEGER,
to_wallet_id INTEGER,
FOREIGN KEY(from_wallet_id) REFERENCES wallets(id),
FOREIGN KEY(to_wallet_id) REFERENCES wallets(id)
);
CREATE TABLE IF NOT EXISTS loans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
person TEXT NOT NULL,
type TEXT NOT NULL,
amount REAL NOT NULL,
currency TEXT NOT NULL,
paid REAL DEFAULT 0,
date TEXT NOT NULL,
note TEXT
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
salt TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS trusted_devices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
device_id TEXT NOT NULL,
device_name TEXT NOT NULL,
last_used TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id)
);
`);
// Run migrations safely
const runMigration = async (sql: string) => {
try { await db.exec(sql); } catch (e) { /* ignore */ }
};
await runMigration(`ALTER TABLE exchanges ADD COLUMN from_wallet_id INTEGER;`);
await runMigration(`ALTER TABLE exchanges ADD COLUMN to_wallet_id INTEGER;`);
await runMigration(`ALTER TABLE transactions ADD COLUMN country_id TEXT;`);
const requiredWallets = [
{ name: 'Cash USD', type: 'cash', currency: 'USD' },
{ name: 'Cash Dinar', type: 'cash', currency: 'IQD' },
{ name: 'USDT', type: 'crypto', currency: 'USD' },
{ name: 'FIB', type: 'bank', currency: 'IQD' },
{ name: 'FastPay', type: 'ewallet', currency: 'IQD' },
{ name: 'WeChat', type: 'ewallet', currency: 'RMB' },
{ name: 'Alipay', type: 'ewallet', currency: 'RMB' },
{ name: 'Super Qi', type: 'ewallet', currency: 'IQD' },
{ name: 'KJ Wallets', type: 'bank', currency: 'USD' }
];
for (const w of requiredWallets) {
const existing = await db.get('SELECT id FROM wallets WHERE name = ?', [w.name]);
if (!existing) {
await db.run(
'INSERT INTO wallets (name, type, currency) VALUES (?, ?, ?)',
[w.name, w.type, w.currency]
);
}
}
// Seed default user 'amez'
const defaultUser = 'amez';
const defaultPass = '902553';
const existingUser = await db.get('SELECT id FROM users WHERE username = ?', [defaultUser]);
if (!existingUser) {
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.pbkdf2Sync(defaultPass, salt, 1000, 64, 'sha512').toString('hex');
await db.run(
'INSERT INTO users (username, password_hash, salt) VALUES (?, ?, ?)',
[defaultUser, hash, salt]
);
}
return dbInstance;
}
|