Files
Ticketsystem/server.js
T
2025-11-15 13:46:44 +01:00

303 lines
10 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import sqlite3 from 'sqlite3';
import path from 'path';
import { fileURLToPath } from 'url';
import moment from 'moment-timezone';
import dotenv from 'dotenv';
// Load environment variables
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const port = 3000;
// Load webhook URLs from environment
const ADMIN_WEBHOOK_URL = process.env.ADMIN_WEBHOOK_URL;
const USER_WEBHOOK_URL = process.env.USER_WEBHOOK_URL;
const timestamp = moment().utc().format('YYYY-MM-DD HH:mm:ss');
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/assets', express.static(path.join(__dirname, 'assets')));
const dbPath = path.resolve(__dirname, 'tickets.db');
const db = new sqlite3.Database(dbPath, err => {
if (err) {
console.error('Failed to connect to DB:', err.message);
} else {
console.log('Connected to SQLite DB.');
}
});
// Create tables
db.serialize(() => {
db.run(`
CREATE TABLE IF NOT EXISTS tickets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
requester TEXT,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
status TEXT,
priority TEXT,
category TEXT,
total_time INTEGER DEFAULT 0
)
`);
db.run(`
CREATE TABLE IF NOT EXISTS time_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
minutes INTEGER NOT NULL,
comment TEXT,
author TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(ticket_id) REFERENCES tickets(id)
)
`);
db.run(`
CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ticket_id INTEGER NOT NULL,
action TEXT NOT NULL,
message TEXT,
author TEXT,
role TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(ticket_id) REFERENCES tickets(id)
)
`);
db.run(`CREATE INDEX IF NOT EXISTS idx_time_logs_ticket ON time_logs(ticket_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_history_ticket ON history(ticket_id)`);
});
// Log history
function logHistory(ticketId, action, message, author = 'system', role = 'user') {
const timestamp = moment().utc().format('YYYY-MM-DD HH:mm:ss');
console.log(`Logging history for ticket: ${ticketId}, timestamp: ${timestamp}`); // Debugging line
db.run(
'INSERT INTO history (ticket_id, action, message, author, role, timestamp) VALUES (?, ?, ?, ?, ?, ?)',
[ticketId, action, message, author, role, timestamp]
);
}
// GET webhook configuration for frontend
app.get('/api/webhooks', (req, res) => {
res.json({
admin: ADMIN_WEBHOOK_URL || null,
user: USER_WEBHOOK_URL || null,
configured: {
admin: !!ADMIN_WEBHOOK_URL,
user: !!USER_WEBHOOK_URL
}
});
});
// GET all tickets
app.get('/api/tickets', (req, res) => {
db.all(
`SELECT id, title, requester, description, status, priority, category, total_time,
strftime('%Y-%m-%dT%H:%M:%S', created_at) AS created_at,
strftime('%Y-%m-%dT%H:%M:%S', updated_at) AS updated_at
FROM tickets
ORDER BY created_at DESC`,
(err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
}
);
});
// GET single ticket
app.get('/api/tickets/:id', (req, res) => {
db.get('SELECT * FROM tickets WHERE id = ?', [req.params.id], (err, row) => {
if (err) return res.status(500).json({ error: err.message });
if (!row) return res.status(404).json({ error: 'Ticket not found' });
res.json(row);
});
});
// GET ticket history (ISO formatted date)
app.get('/api/tickets/:id/history', (req, res) => {
db.all(
`SELECT id, ticket_id, action, message, author, role,
strftime('%Y-%m-%dT%H:%M:%S', timestamp) AS date
FROM history
WHERE ticket_id = ?
ORDER BY timestamp DESC`,
[req.params.id],
(err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
}
);
});
// POST new ticket
app.post('/api/tickets', (req, res) => {
const { title, requester, description, status, priority, category } = req.body;
const statusValue = status && status.trim() ? status : 'open';
// Get current Berlin time and convert it to UTC
const berlinTime = moment.tz('Europe/Berlin');
const utcTime = berlinTime.clone().utc().format('YYYY-MM-DD HH:mm:ss'); // Convert Berlin time to UTC
db.run(
'INSERT INTO tickets (title, requester, description, status, priority, category, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[title, requester, description, statusValue, priority, category, utcTime, utcTime],
function (err) {
if (err) return res.status(500).json({ error: err.message });
logHistory(this.lastID, 'Ticket Created', `Ticket "${title}" created`, requester);
res.status(201).json({ id: this.lastID });
}
);
});
// PUT update ticket
app.put('/api/tickets/:id', (req, res) => {
const { title, requester, description, status, priority, category, author } = req.body;
// Get current Berlin time and convert it to UTC
const berlinTime = moment.tz('Europe/Berlin');
const utcTime = berlinTime.clone().utc().format('YYYY-MM-DD HH:mm:ss'); // Convert Berlin time to UTC
db.run(
`UPDATE tickets SET
title = ?,
requester = ?,
description = ?,
status = ?,
priority = ?,
category = ?,
updated_at = ?
WHERE id = ?`,
[title, requester, description, status, priority, category, utcTime, req.params.id],
function (err) {
if (err) return res.status(500).json({ error: err.message });
if (this.changes === 0) return res.status(404).json({ error: 'Ticket not found' });
logHistory(req.params.id, 'Ticket Updated', 'Ticket details updated', author || 'system');
res.json({ message: 'Ticket updated' });
}
);
});
// DELETE ticket
app.delete('/api/tickets/:id', (req, res) => {
db.run('DELETE FROM tickets WHERE id = ?', [req.params.id], function (err) {
if (err) return res.status(500).json({ error: err.message });
if (this.changes === 0) return res.status(404).json({ error: 'Ticket not found' });
logHistory(req.params.id, 'Ticket Deleted', 'Ticket deleted');
res.json({ message: 'Ticket deleted' });
});
});
// POST comment with role
app.post('/api/tickets/:id/comments', (req, res) => {
const { author, role, content } = req.body;
if (!author || !content) return res.status(400).json({ error: 'Author and content required' });
const timestamp = moment().utc().format('YYYY-MM-DD HH:mm:ss'); // Explicit UTC timestamp
db.run(
'INSERT INTO history (ticket_id, action, message, author, role, timestamp) VALUES (?, "Comment", ?, ?, ?, ?)',
[req.params.id, content, author, role || 'user', timestamp],
function (err) {
if (err) return res.status(500).json({ error: err.message });
res.status(201).json({ id: this.lastID });
}
);
});
// GET comments (ISO formatted date)
app.get('/api/tickets/:id/comments', (req, res) => {
db.all(
`SELECT id, ticket_id, message AS content, author, role,
strftime('%Y-%m-%dT%H:%M:%S', timestamp) AS date
FROM history
WHERE ticket_id = ? AND action = "Comment"
ORDER BY timestamp DESC`,
[req.params.id],
(err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
}
);
});
// POST time log
app.post('/api/tickets/:id/time', (req, res) => {
const { minutes, comment, author } = req.body;
if (typeof minutes !== 'number' || minutes <= 0)
return res.status(400).json({ error: 'Minutes must be a positive number.' });
if (!author)
return res.status(400).json({ error: 'Author is required.' });
db.get('SELECT * FROM tickets WHERE id = ?', [req.params.id], (err, ticket) => {
if (err) return res.status(500).json({ error: err.message });
if (!ticket) return res.status(404).json({ error: 'Ticket not found' });
db.run(
'INSERT INTO time_logs (ticket_id, minutes, comment, author) VALUES (?, ?, ?, ?)',
[req.params.id, minutes, comment || '', author],
function (err) {
if (err) return res.status(500).json({ error: err.message });
db.get(
'SELECT SUM(minutes) as total FROM time_logs WHERE ticket_id = ?',
[req.params.id],
(err, row) => {
if (err) return res.status(500).json({ error: err.message });
const totalTime = row.total || 0;
db.run(
'UPDATE tickets SET total_time = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
[totalTime, req.params.id],
err => {
if (err) return res.status(500).json({ error: err.message });
logHistory(
req.params.id,
'Time Logged',
`Added ${minutes} minutes by ${author}. Comment: ${comment || 'none'}`,
author
);
// Send back the updated total_time to the frontend
res.status(201).json({ message: 'Time logged', total_time: totalTime });
}
);
}
);
}
);
});
});
// GET time logs
app.get('/api/tickets/:id/time', (req, res) => {
db.all(
'SELECT * FROM time_logs WHERE ticket_id = ? ORDER BY created_at DESC',
[req.params.id],
(err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
}
);
});
app.listen(port, () => {
console.log(`Server läuft auf http://localhost:${port}`);
console.log('Webhook URLs loaded:', {
admin: ADMIN_WEBHOOK_URL ? 'Configured' : 'Not configured',
user: USER_WEBHOOK_URL ? 'Configured' : 'Not configured'
});
});