Spaces:
Sleeping
Sleeping
| import express from 'express'; | |
| import cors from 'cors'; | |
| import { GoogleGenAI } from '@google/genai'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import sqlite3 from 'sqlite3'; | |
| import bcrypt from 'bcryptjs'; | |
| const app = express(); | |
| // Hugging Face Spaces require port 7860 | |
| const port = process.env.PORT || 7860; | |
| // --- Database Initialization --- | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| const dbPath = path.join(__dirname, 'database.db'); | |
| const db = new sqlite3.Database(dbPath); | |
| db.serialize(() => { | |
| // Users table | |
| db.run(` | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| username TEXT UNIQUE NOT NULL, | |
| password_hash TEXT NOT NULL, | |
| role TEXT DEFAULT 'Standard Node', | |
| created_at DATETIME DEFAULT CURRENT_TIMESTAMP | |
| ) | |
| `); | |
| // Messages table for Neural Memory | |
| db.run(` | |
| CREATE TABLE IF NOT EXISTS messages ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| user_id INTEGER, | |
| role TEXT NOT NULL, | |
| content TEXT NOT NULL, | |
| timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, | |
| FOREIGN KEY(user_id) REFERENCES users(id) | |
| ) | |
| `); | |
| }); | |
| // --- Simulated In-Memory Session Storage --- | |
| let activeSession = null; | |
| // --- Mock Citi Financial Data --- | |
| const CITI_ACCOUNTS = { | |
| accountGroupDetails: [ | |
| { | |
| accountGroup: "CHECKING", | |
| checkingAccountsDetails: [ | |
| { | |
| productName: "Corporate Mastery Checking", | |
| accountNickname: "Main Ops Node", | |
| accountDescription: "Corporate Mastery Checking - 9594", | |
| balanceType: "ASSET", | |
| displayAccountNumber: "XXXXXX9594", | |
| accountId: "citi_acc_99201", | |
| currencyCode: "USD", | |
| accountStatus: "ACTIVE", | |
| currentBalance: 1245000.50, | |
| availableBalance: 1240000.00 | |
| } | |
| ], | |
| totalCurrentBalance: { localCurrencyCode: "USD", localCurrencyBalanceAmount: 1245000.50 }, | |
| totalAvailableBalance: { localCurrencyCode: "USD", localCurrencyBalanceAmount: 1240000.00 } | |
| } | |
| ], | |
| customer: { | |
| customerId: "citi_cust_884102" | |
| } | |
| }; | |
| const CITI_TRANSACTIONS = { | |
| "citi_acc_99201": { | |
| checkingAccountTransactions: [ | |
| { | |
| accountId: "citi_acc_99201", | |
| currencyCode: "USD", | |
| transactionAmount: -25000.00, | |
| transactionDate: "2024-03-31", | |
| transactionDescription: "NEURAL_NETWORK_COMPUTE_Q1_ALLOCATION", | |
| transactionId: "TXN_C_884102", | |
| transactionStatus: "POSTED", | |
| transactionType: "PAYMENT", | |
| displayAccountNumber: "XXXXXX9594" | |
| } | |
| ] | |
| } | |
| }; | |
| app.use(cors()); | |
| app.use(express.json()); | |
| app.use(express.urlencoded({ extended: true })); | |
| // --- Authentication Endpoints --- | |
| app.get('/api/auth/me', (req, res) => { | |
| if (activeSession) { | |
| res.json({ isAuthenticated: true, user: activeSession }); | |
| } else { | |
| res.status(401).json({ isAuthenticated: false, user: null }); | |
| } | |
| }); | |
| app.post('/api/auth/register', async (req, res) => { | |
| const { username, password } = req.body; | |
| if (!username || !password) return res.status(400).json({ error: 'Identity credentials missing.' }); | |
| try { | |
| const hash = await bcrypt.hash(password, 10); | |
| db.run( | |
| 'INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)', | |
| [username, hash, 'Root Admin'], | |
| function(err) { | |
| if (err) { | |
| if (err.message.includes('UNIQUE')) { | |
| return res.status(409).json({ error: 'Identity node already registered.' }); | |
| } | |
| return res.status(500).json({ error: 'Registry write failure.' }); | |
| } | |
| res.status(201).json({ success: true, userId: this.lastID }); | |
| } | |
| ); | |
| } catch (error) { | |
| res.status(500).json({ error: 'Encryption engine failure.' }); | |
| } | |
| }); | |
| app.post('/api/auth/login', (req, res) => { | |
| const { username, password } = req.body; | |
| db.get('SELECT * FROM users WHERE username = ?', [username], async (err, user) => { | |
| if (err || !user) { | |
| return res.status(401).json({ success: false, message: 'Identity node rejected credentials.' }); | |
| } | |
| const match = await bcrypt.compare(password, user.password_hash); | |
| if (match) { | |
| activeSession = { | |
| id: `USR-${user.id}`, | |
| dbId: user.id, // Internal database ID for relations | |
| name: user.username, | |
| role: user.role, | |
| lastLogin: new Date().toISOString() | |
| }; | |
| res.json({ success: true, user: activeSession }); | |
| } else { | |
| res.status(401).json({ success: false, message: 'Identity node rejected credentials.' }); | |
| } | |
| }); | |
| }); | |
| app.post('/api/auth/logout', (req, res) => { | |
| activeSession = null; | |
| res.json({ success: true }); | |
| }); | |
| // --- Chat History Endpoints --- | |
| app.get('/api/chat/history', (req, res) => { | |
| if (!activeSession) return res.status(401).json({ error: 'Session parity lost.' }); | |
| db.all( | |
| 'SELECT * FROM messages WHERE user_id = ? ORDER BY timestamp ASC', | |
| [activeSession.dbId], | |
| (err, rows) => { | |
| if (err) return res.status(500).json({ error: 'Neural memory retrieval failure.' }); | |
| res.json(rows); | |
| } | |
| ); | |
| }); | |
| // --- Gemini Proxy --- | |
| app.post('/api/gemini/generate', async (req, res) => { | |
| try { | |
| const apiKey = process.env.API_KEY; | |
| if (!apiKey) return res.status(500).json({ error: 'API Key missing' }); | |
| const { model, contents, config, saveToMemory } = req.body; | |
| const ai = new GoogleGenAI({ apiKey }); | |
| const response = await ai.models.generateContent({ | |
| model: model || 'gemini-3-flash-preview', | |
| contents, | |
| config: config || {} | |
| }); | |
| const aiText = response.text; | |
| // Persist to memory if requested and authenticated | |
| if (activeSession && saveToMemory && contents[0]?.parts[0]?.text) { | |
| const userPrompt = contents[0].parts[0].text; | |
| // Save user prompt | |
| db.run('INSERT INTO messages (user_id, role, content) VALUES (?, ?, ?)', [activeSession.dbId, 'user', userPrompt]); | |
| // Save AI response | |
| db.run('INSERT INTO messages (user_id, role, content) VALUES (?, ?, ?)', [activeSession.dbId, 'assistant', aiText]); | |
| } | |
| res.json({ text: aiText, candidates: response.candidates }); | |
| } catch (error) { | |
| res.status(500).json({ error: 'AI communication failure', details: error.message }); | |
| } | |
| }); | |
| const CITI_BASE = '/api/accounts/account-transactions/partner/v1'; | |
| app.get(`${CITI_BASE}/accounts/details`, (req, res) => res.json(CITI_ACCOUNTS)); | |
| app.get(`${CITI_BASE}/accounts/:accountId/transactions`, (req, res) => { | |
| const { accountId } = req.params; | |
| res.json(CITI_TRANSACTIONS[accountId] || { checkingAccountTransactions: [] }); | |
| }); | |
| // Serve frontend files from the dist directory | |
| app.use(express.static(path.join(__dirname, 'dist'))); | |
| // SPA Fallback: Catch any request that didn't match an API route or a static file. | |
| // In Express 5, using a middleware function without a path is the safest way | |
| // to implement a catch-all fallback for Single Page Applications. | |
| app.use((req, res) => { | |
| // Safeguard: Do not serve index.html for missing API routes | |
| if (req.path.startsWith('/api')) { | |
| return res.status(404).json({ error: 'API endpoint not found' }); | |
| } | |
| res.sendFile(path.join(__dirname, 'dist', 'index.html')); | |
| }); | |
| app.listen(port, '0.0.0.0', () => console.log(`Financial Node Proxy Operational on port ${port}`)); |