Dateien nach "/" hochladen
This commit is contained in:
commit
08631eaab5
5 files changed
+2777
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
ADMIN_WEBHOOK_URL=https://discord.com/api/webhooks/1402699603291275327/_yzlhPJt9-Qoc5-FysQDhzXRuJKBJp6nCAbvG4O3OBNCZ0esmr0DJhRDQR5LxzmiLZPT
|
||||||
|
USER_WEBHOOK_URL=https://discord.com/api/webhooks/1401318995122061343/pJ6ShlG65cM3YoZnN37mT2julv-eDNYid9IyzNBxuzN5iYEmfWVkxfqDuIt4I9z0kTSu
|
||||||
Generated
+2447
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "ticketsystem",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^2.2.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"date-fns-tz": "^3.2.0",
|
||||||
|
"dotenv": "^17.2.1",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"luxon": "^3.7.1",
|
||||||
|
"moment-timezone": "^0.6.0",
|
||||||
|
"multer": "^2.0.2",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
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'
|
||||||
|
});
|
||||||
|
});
|
||||||
BIN
Binary file not shown.
Reference in new issue
Block a user