fomat
This commit is contained in:
547
backend/server.js
Normal file
547
backend/server.js
Normal file
@@ -0,0 +1,547 @@
|
||||
// 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();
|
||||
Reference in New Issue
Block a user