548 lines
20 KiB
JavaScript
548 lines
20 KiB
JavaScript
// 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 from /public
|
|
const path = require('path');
|
|
const publicDir = path.join(__dirname, '..', 'public');
|
|
app.use(express.static(publicDir));
|
|
|
|
// Root route
|
|
app.get('/', (req, res) => {
|
|
res.sendFile(path.join(publicDir, '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();
|