// Backend Server for AccManager // Express.js + mssql const express = require('express'); const sql = require('mssql'); const cors = require('cors'); const app = express(); // Middleware app.use(cors()); app.use(express.json()); // Serve static files const path = require('path'); app.use(express.static(path.join(__dirname, 'pages'))); app.use(express.static(path.join(__dirname, 'js'))); app.use(express.static(path.join(__dirname))); // Root route app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'pages', 'login.html')); }); // SQL Server Configuration const sqlConfig = { server: '172.20.235.176', authentication: { type: 'default', options: { userName: 'sa', password: 'robotics@2022' } }, options: { database: 'AccManager', trustServerCertificate: true, enableKeepAlive: true, connectTimeout: 30000, encrypt: false } }; // Initialize Database Pool let pool; async function initializeDatabase() { try { pool = new sql.ConnectionPool(sqlConfig); await pool.connect(); console.log('✓ Connected to SQL Server'); // Check and create database if not exists const masterConnection = new sql.ConnectionPool({ server: '172.20.235.176', authentication: { type: 'default', options: { userName: 'sa', password: 'robotics@2022' } }, options: { connectTimeout: 30000, database: 'master', trustServerCertificate: true, encrypt: false } }); await masterConnection.connect(); const createDbResult = await masterConnection.request() .query(`IF NOT EXISTS (SELECT name FROM sys.databases WHERE name = 'AccManager') BEGIN CREATE DATABASE AccManager; END`); await masterConnection.close(); // Now create tables in AccManager await createTables(); console.log('✓ Database and tables created'); } catch (err) { console.error('Database connection failed:', err); process.exit(1); } } async function createTables() { const queries = [ // Users Table `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Users') BEGIN CREATE TABLE Users ( UserId INT PRIMARY KEY IDENTITY(1,1), Username NVARCHAR(50) UNIQUE NOT NULL, Password NVARCHAR(255) NOT NULL, Email NVARCHAR(100), FullName NVARCHAR(100), Role NVARCHAR(50) NOT NULL, Status NVARCHAR(20) DEFAULT 'Active', CreatedDate DATETIME DEFAULT GETDATE(), LastLogin DATETIME, IsActive BIT DEFAULT 1 ) END`, // Applications Table `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Applications') BEGIN CREATE TABLE Applications ( AppId INT PRIMARY KEY IDENTITY(1,1), Name NVARCHAR(100) NOT NULL, Type NVARCHAR(50), Status NVARCHAR(20) DEFAULT 'online', Icon NVARCHAR(50), Description NVARCHAR(500), Url NVARCHAR(255), CreatedDate DATETIME DEFAULT GETDATE(), UpdatedDate DATETIME DEFAULT GETDATE() ) END`, // Accounts Table `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Accounts') BEGIN CREATE TABLE Accounts ( AccountId INT PRIMARY KEY IDENTITY(1,1), UserId INT NOT NULL, AppId INT NOT NULL, AccountUsername NVARCHAR(100), AccountPassword NVARCHAR(255), Email NVARCHAR(100), AccessLevel NVARCHAR(50), Status NVARCHAR(20) DEFAULT 'Active', Notes NVARCHAR(MAX), CreatedDate DATETIME DEFAULT GETDATE(), UpdatedDate DATETIME DEFAULT GETDATE(), FOREIGN KEY (UserId) REFERENCES Users(UserId) ON DELETE CASCADE, FOREIGN KEY (AppId) REFERENCES Applications(AppId) ON DELETE CASCADE ) END`, // AuditLog Table `IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'AuditLog') BEGIN CREATE TABLE AuditLog ( LogId INT PRIMARY KEY IDENTITY(1,1), UserId INT, Action NVARCHAR(50), TableName NVARCHAR(50), RecordId INT, OldValue NVARCHAR(MAX), NewValue NVARCHAR(MAX), Timestamp DATETIME DEFAULT GETDATE(), FOREIGN KEY (UserId) REFERENCES Users(UserId) ) END` ]; for (let query of queries) { try { await pool.request().query(query); } catch (err) { console.error('Table creation error:', err.message); } } // Ensure new columns exist on Applications for migrations try { await pool.request().query(`IF COL_LENGTH('dbo.Applications','Url') IS NULL ALTER TABLE Applications ADD Url NVARCHAR(255);`); await pool.request().query(`IF COL_LENGTH('dbo.Applications','Description') IS NULL ALTER TABLE Applications ADD Description NVARCHAR(500);`); // Backfill Url to empty string to avoid undefined in responses await pool.request().query(`UPDATE Applications SET Url = '' WHERE Url IS NULL;`); } catch (err) { console.error('Column addition error (Applications):', err.message); } // Insert initial admin user try { await pool.request() .input('username', sql.NVarChar, 'admin') .input('password', sql.NVarChar, 'admin') .input('email', sql.NVarChar, 'admin@accmanager.local') .input('fullname', sql.NVarChar, 'Administrator') .input('role', sql.NVarChar, 'admin') .query(`IF NOT EXISTS (SELECT * FROM Users WHERE Username = @username) INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive) VALUES (@username, @password, @email, @fullname, @role, 1)`); console.log('✓ Admin user created: admin / admin'); } catch (err) { console.error('Admin user error:', err.message); } // Insert sample applications try { await pool.request() .query(`IF (SELECT COUNT(*) FROM Applications) = 0 BEGIN INSERT INTO Applications (Name, Type, Status, Icon, Description, Url) VALUES ('AWS', 'Cloud', 'online', 'cloud', 'Amazon Web Services', 'https://aws.amazon.com'), ('GitHub', 'VCS', 'online', 'code', 'GitHub - Version Control', 'https://github.com'), ('Google Workspace', 'Collaboration', 'online', 'mail', 'Google Workspace', 'https://workspace.google.com'), ('Nginx Proxy', 'Infra', 'offline', 'dns', 'Nginx Web Server', 'https://nginx.org') END`); console.log('✓ Sample applications created'); } catch (err) { console.error('Applications error:', err.message); } } // ========================================== // API ROUTES - Authentication // ========================================== // Login endpoint app.post('/api/auth/login', async (req, res) => { try { const { username, password } = req.body; const result = await pool.request() .input('username', sql.NVarChar, username) .input('password', sql.NVarChar, password) .query('SELECT UserId, Username, Email, FullName, Role, Status FROM Users WHERE Username = @username AND Password = @password AND IsActive = 1'); if (result.recordset.length > 0) { const user = result.recordset[0]; // Update last login await pool.request() .input('userId', sql.Int, user.UserId) .query('UPDATE Users SET LastLogin = GETDATE() WHERE UserId = @userId'); res.json({ success: true, message: 'Login successful', user: user }); } else { res.status(401).json({ success: false, message: 'Invalid username or password' }); } } catch (err) { console.error('Login error:', err); res.status(500).json({ success: false, message: err.message }); } }); // ========================================== // API ROUTES - Users // ========================================== // Get all users app.get('/api/users', async (req, res) => { try { const result = await pool.request() .query('SELECT UserId, Username, Email, FullName, Role, Status, CreatedDate FROM Users ORDER BY CreatedDate DESC'); res.json({ success: true, data: result.recordset }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Get user by ID app.get('/api/users/:id', async (req, res) => { try { const result = await pool.request() .input('userId', sql.Int, req.params.id) .query('SELECT * FROM Users WHERE UserId = @userId'); if (result.recordset.length > 0) { res.json({ success: true, data: result.recordset[0] }); } else { res.status(404).json({ success: false, message: 'User not found' }); } } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Create new user app.post('/api/users', async (req, res) => { try { const { username, password, email, fullname, role } = req.body; const result = await pool.request() .input('username', sql.NVarChar, username) .input('password', sql.NVarChar, password) .input('email', sql.NVarChar, email) .input('fullname', sql.NVarChar, fullname) .input('role', sql.NVarChar, role) .query(`INSERT INTO Users (Username, Password, Email, FullName, Role, IsActive) VALUES (@username, @password, @email, @fullname, @role, 1); SELECT SCOPE_IDENTITY() as UserId`); res.json({ success: true, message: 'User created', userId: result.recordset[0].UserId }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // ========================================== // API ROUTES - Applications // ========================================== // Get all applications app.get('/api/applications', async (req, res) => { try { const result = await pool.request() .query('SELECT AppId, Name, Type, Status, Icon, Description, Url, CreatedDate, UpdatedDate FROM Applications ORDER BY Name'); res.json({ success: true, data: result.recordset }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Create application app.post('/api/applications', async (req, res) => { try { const { name, type, status, icon, description, url } = req.body; const result = await pool.request() .input('name', sql.NVarChar, name) .input('type', sql.NVarChar, type) .input('status', sql.NVarChar, status) .input('icon', sql.NVarChar, icon) .input('description', sql.NVarChar, description) .input('url', sql.NVarChar, url) .query(`INSERT INTO Applications (Name, Type, Status, Icon, Description, Url) VALUES (@name, @type, @status, @icon, @description, @url); SELECT SCOPE_IDENTITY() as AppId`); res.json({ success: true, message: 'Application created', appId: result.recordset[0].AppId }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Update application app.put('/api/applications/:id', async (req, res) => { try { const { name, type, status, icon, description, url } = req.body; await pool.request() .input('appId', sql.Int, req.params.id) .input('name', sql.NVarChar, name) .input('type', sql.NVarChar, type) .input('status', sql.NVarChar, status) .input('icon', sql.NVarChar, icon) .input('description', sql.NVarChar, description) .input('url', sql.NVarChar, url) .query(`UPDATE Applications SET Name = @name, Type = @type, Status = @status, Icon = @icon, Description = @description, Url = @url, UpdatedDate = GETDATE() WHERE AppId = @appId`); res.json({ success: true, message: 'Application updated' }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Delete application app.delete('/api/applications/:id', async (req, res) => { try { await pool.request() .input('appId', sql.Int, req.params.id) .query('DELETE FROM Applications WHERE AppId = @appId'); res.json({ success: true, message: 'Application deleted' }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // ========================================== // API ROUTES - Accounts // ========================================== // Get accounts for a user app.get('/api/accounts/user/:userId', async (req, res) => { try { const result = await pool.request() .input('userId', sql.Int, req.params.userId) .query(`SELECT a.*, app.Name as AppName, app.Type as AppType FROM Accounts a JOIN Applications app ON a.AppId = app.AppId WHERE a.UserId = @userId ORDER BY a.CreatedDate DESC`); res.json({ success: true, data: result.recordset }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Create account app.post('/api/accounts', async (req, res) => { try { const { userId, appId, accountUsername, accountPassword, email, accessLevel, notes } = req.body; const result = await pool.request() .input('userId', sql.Int, userId) .input('appId', sql.Int, appId) .input('accountUsername', sql.NVarChar, accountUsername) .input('accountPassword', sql.NVarChar, accountPassword) .input('email', sql.NVarChar, email) .input('accessLevel', sql.NVarChar, accessLevel) .input('notes', sql.NVarChar, notes) .query(`INSERT INTO Accounts (UserId, AppId, AccountUsername, AccountPassword, Email, AccessLevel, Notes) VALUES (@userId, @appId, @accountUsername, @accountPassword, @email, @accessLevel, @notes); SELECT SCOPE_IDENTITY() as AccountId`); res.json({ success: true, message: 'Account created', accountId: result.recordset[0].AccountId }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Update account app.put('/api/accounts/:id', async (req, res) => { try { const { userId, appId, accountUsername, accountPassword, email, accessLevel, notes } = req.body; await pool.request() .input('accountId', sql.Int, req.params.id) .input('userId', sql.Int, userId) .input('appId', sql.Int, appId) .input('accountUsername', sql.NVarChar, accountUsername) .input('accountPassword', sql.NVarChar, accountPassword) .input('email', sql.NVarChar, email) .input('accessLevel', sql.NVarChar, accessLevel) .input('notes', sql.NVarChar, notes) .query(`UPDATE Accounts SET UserId = @userId, AppId = @appId, AccountUsername = @accountUsername, AccountPassword = @accountPassword, Email = @email, AccessLevel = @accessLevel, Notes = @notes, UpdatedDate = GETDATE() WHERE AccountId = @accountId`); res.json({ success: true, message: 'Account updated' }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Delete account app.delete('/api/accounts/:id', async (req, res) => { try { await pool.request() .input('accountId', sql.Int, req.params.id) .query('DELETE FROM Accounts WHERE AccountId = @accountId'); res.json({ success: true, message: 'Account deleted' }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // ========================================== // API ROUTES - Database Info // ========================================== // Get database information app.get('/api/database/info', async (req, res) => { try { const tables = await pool.request().query(` SELECT TABLE_NAME as TableName, (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = t.TABLE_NAME) as ColumnCount FROM INFORMATION_SCHEMA.TABLES t WHERE TABLE_SCHEMA = 'dbo' ORDER BY TABLE_NAME `); const users = await pool.request().query('SELECT COUNT(*) as Count FROM Users'); const apps = await pool.request().query('SELECT COUNT(*) as Count FROM Applications'); const accounts = await pool.request().query('SELECT COUNT(*) as Count FROM Accounts'); res.json({ success: true, database: 'AccManager', server: '172.20.235.176', tables: tables.recordset, statistics: { users: users.recordset[0].Count, applications: apps.recordset[0].Count, accounts: accounts.recordset[0].Count } }); } catch (err) { res.status(500).json({ success: false, message: err.message }); } }); // Health check app.get('/api/health', (req, res) => { res.json({ status: 'OK', database: 'Connected' }); }); // ========================================== // Error Handling // ========================================== app.use((err, req, res, next) => { console.error(err); res.status(500).json({ success: false, message: err.message }); }); // ========================================== // Server Startup // ========================================== const PORT = process.env.PORT || 3000; async function startServer() { try { await initializeDatabase(); app.listen(PORT, () => { console.log(`\n========================================`); console.log(`AccManager Backend Server`); console.log(`========================================`); console.log(`✓ Server running on http://localhost:${PORT}`); console.log(`✓ Database: AccManager`); console.log(`✓ Default admin: admin / admin`); console.log(`\nAPI Endpoints:`); console.log(` POST /api/auth/login`); console.log(` GET /api/database/info`); console.log(` GET /api/users`); console.log(` GET /api/applications`); console.log(` GET /api/accounts/user/:userId`); console.log(`========================================\n`); }); } catch (err) { console.error('Failed to start server:', err); process.exit(1); } } // Graceful shutdown process.on('SIGINT', async () => { console.log('\nShutting down...'); if (pool) { await pool.close(); } process.exit(0); }); startServer();