303 lines
10 KiB
JavaScript
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'
|
|
});
|
|
}); |