fix permissions
This commit is contained in:
@@ -363,6 +363,108 @@ class IssueController {
|
||||
}
|
||||
}
|
||||
|
||||
async getIssueStatuses(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const items = await issueService.getIssueStatuses();
|
||||
res.status(200).json({ success: true, data: items });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.getIssueStatuses error', { error });
|
||||
res.status(500).json({ success: false, message: 'Issue-Status konnten nicht geladen werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async createIssueStatus(req: Request, res: Response): Promise<void> {
|
||||
const { schluessel, bezeichnung } = req.body;
|
||||
if (!schluessel?.trim() || !bezeichnung?.trim()) {
|
||||
res.status(400).json({ success: false, message: 'Schlüssel und Bezeichnung sind erforderlich' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const item = await issueService.createIssueStatus(req.body);
|
||||
res.status(201).json({ success: true, data: item });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.createIssueStatus error', { error });
|
||||
res.status(500).json({ success: false, message: 'Issue-Status konnte nicht erstellt werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateIssueStatus(req: Request, res: Response): Promise<void> {
|
||||
const id = parseInt(param(req, 'id'), 10);
|
||||
if (isNaN(id)) { res.status(400).json({ success: false, message: 'Ungültige ID' }); return; }
|
||||
try {
|
||||
const item = await issueService.updateIssueStatus(id, req.body);
|
||||
if (!item) { res.status(404).json({ success: false, message: 'Nicht gefunden' }); return; }
|
||||
res.status(200).json({ success: true, data: item });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.updateIssueStatus error', { error });
|
||||
res.status(500).json({ success: false, message: 'Issue-Status konnte nicht aktualisiert werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteIssueStatus(req: Request, res: Response): Promise<void> {
|
||||
const id = parseInt(param(req, 'id'), 10);
|
||||
if (isNaN(id)) { res.status(400).json({ success: false, message: 'Ungültige ID' }); return; }
|
||||
try {
|
||||
const item = await issueService.deleteIssueStatus(id);
|
||||
if (!item) { res.status(404).json({ success: false, message: 'Nicht gefunden' }); return; }
|
||||
res.status(200).json({ success: true, data: item });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.deleteIssueStatus error', { error });
|
||||
res.status(500).json({ success: false, message: 'Issue-Status konnte nicht deaktiviert werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async getIssuePriorities(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const items = await issueService.getIssuePriorities();
|
||||
res.status(200).json({ success: true, data: items });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.getIssuePriorities error', { error });
|
||||
res.status(500).json({ success: false, message: 'Prioritäten konnten nicht geladen werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async createIssuePriority(req: Request, res: Response): Promise<void> {
|
||||
const { schluessel, bezeichnung } = req.body;
|
||||
if (!schluessel?.trim() || !bezeichnung?.trim()) {
|
||||
res.status(400).json({ success: false, message: 'Schlüssel und Bezeichnung sind erforderlich' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const item = await issueService.createIssuePriority(req.body);
|
||||
res.status(201).json({ success: true, data: item });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.createIssuePriority error', { error });
|
||||
res.status(500).json({ success: false, message: 'Priorität konnte nicht erstellt werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateIssuePriority(req: Request, res: Response): Promise<void> {
|
||||
const id = parseInt(param(req, 'id'), 10);
|
||||
if (isNaN(id)) { res.status(400).json({ success: false, message: 'Ungültige ID' }); return; }
|
||||
try {
|
||||
const item = await issueService.updateIssuePriority(id, req.body);
|
||||
if (!item) { res.status(404).json({ success: false, message: 'Nicht gefunden' }); return; }
|
||||
res.status(200).json({ success: true, data: item });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.updateIssuePriority error', { error });
|
||||
res.status(500).json({ success: false, message: 'Priorität konnte nicht aktualisiert werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteIssuePriority(req: Request, res: Response): Promise<void> {
|
||||
const id = parseInt(param(req, 'id'), 10);
|
||||
if (isNaN(id)) { res.status(400).json({ success: false, message: 'Ungültige ID' }); return; }
|
||||
try {
|
||||
const item = await issueService.deleteIssuePriority(id);
|
||||
if (!item) { res.status(404).json({ success: false, message: 'Nicht gefunden' }); return; }
|
||||
res.status(200).json({ success: true, data: item });
|
||||
} catch (error) {
|
||||
logger.error('IssueController.deleteIssuePriority error', { error });
|
||||
res.status(500).json({ success: false, message: 'Priorität konnte nicht deaktiviert werden' });
|
||||
}
|
||||
}
|
||||
|
||||
async getStatusmeldungen(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const items = await issueService.getStatusmeldungen();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
-- Migration 058: dynamic issue statuses and priorities
|
||||
|
||||
CREATE TABLE IF NOT EXISTS issue_statuses (
|
||||
id SERIAL PRIMARY KEY,
|
||||
schluessel VARCHAR(50) UNIQUE NOT NULL,
|
||||
bezeichnung VARCHAR(100) NOT NULL,
|
||||
farbe VARCHAR(50) NOT NULL DEFAULT 'default',
|
||||
ist_abschluss BOOLEAN NOT NULL DEFAULT false,
|
||||
ist_initial BOOLEAN NOT NULL DEFAULT false,
|
||||
benoetigt_typ_freigabe BOOLEAN NOT NULL DEFAULT false,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
aktiv BOOLEAN NOT NULL DEFAULT true
|
||||
);
|
||||
|
||||
INSERT INTO issue_statuses (schluessel, bezeichnung, farbe, ist_abschluss, ist_initial, benoetigt_typ_freigabe, sort_order)
|
||||
VALUES
|
||||
('offen', 'Offen', 'info', false, true, false, 0),
|
||||
('in_bearbeitung', 'In Bearbeitung', 'warning', false, false, false, 1),
|
||||
('erledigt', 'Erledigt', 'success', true, false, false, 2),
|
||||
('abgelehnt', 'Abgelehnt', 'error', true, false, true, 3)
|
||||
ON CONFLICT (schluessel) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS issue_prioritaeten (
|
||||
id SERIAL PRIMARY KEY,
|
||||
schluessel VARCHAR(50) UNIQUE NOT NULL,
|
||||
bezeichnung VARCHAR(100) NOT NULL,
|
||||
farbe VARCHAR(50) NOT NULL DEFAULT '#9e9e9e',
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
aktiv BOOLEAN NOT NULL DEFAULT true
|
||||
);
|
||||
|
||||
INSERT INTO issue_prioritaeten (schluessel, bezeichnung, farbe, sort_order)
|
||||
VALUES
|
||||
('hoch', 'Hoch', '#d32f2f', 0),
|
||||
('mittel', 'Mittel', '#ed6c02', 1),
|
||||
('niedrig', 'Niedrig', '#9e9e9e', 2)
|
||||
ON CONFLICT (schluessel) DO NOTHING;
|
||||
@@ -41,6 +41,18 @@ router.get(
|
||||
issueController.getWidgetSummary.bind(issueController)
|
||||
);
|
||||
|
||||
// --- Statuses CRUD (BEFORE /:id) ---
|
||||
router.get('/statuses', authenticate, issueController.getIssueStatuses.bind(issueController));
|
||||
router.post('/statuses', authenticate, requirePermission('issues:edit_settings'), issueController.createIssueStatus.bind(issueController));
|
||||
router.patch('/statuses/:id', authenticate, requirePermission('issues:edit_settings'), issueController.updateIssueStatus.bind(issueController));
|
||||
router.delete('/statuses/:id', authenticate, requirePermission('issues:edit_settings'), issueController.deleteIssueStatus.bind(issueController));
|
||||
|
||||
// --- Priorities CRUD (BEFORE /:id) ---
|
||||
router.get('/priorities', authenticate, issueController.getIssuePriorities.bind(issueController));
|
||||
router.post('/priorities', authenticate, requirePermission('issues:edit_settings'), issueController.createIssuePriority.bind(issueController));
|
||||
router.patch('/priorities/:id', authenticate, requirePermission('issues:edit_settings'), issueController.updateIssuePriority.bind(issueController));
|
||||
router.delete('/priorities/:id', authenticate, requirePermission('issues:edit_settings'), issueController.deleteIssuePriority.bind(issueController));
|
||||
|
||||
// --- Type management routes (BEFORE /:id to avoid conflict) ---
|
||||
router.get(
|
||||
'/typen',
|
||||
|
||||
@@ -363,20 +363,202 @@ async function getAssignableMembers() {
|
||||
|
||||
async function getIssueCounts() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT status, COUNT(*)::int AS count FROM issues GROUP BY status`
|
||||
);
|
||||
const counts: Record<string, number> = { offen: 0, in_bearbeitung: 0, erledigt: 0, abgelehnt: 0 };
|
||||
for (const row of result.rows) {
|
||||
counts[row.status] = row.count;
|
||||
}
|
||||
return counts;
|
||||
const result = await pool.query(`
|
||||
SELECT
|
||||
COALESCE(s.schluessel, i.status) AS schluessel,
|
||||
COALESCE(s.bezeichnung, i.status) AS bezeichnung,
|
||||
COALESCE(s.farbe, 'default') AS farbe,
|
||||
COALESCE(s.ist_abschluss, false) AS ist_abschluss,
|
||||
COALESCE(s.sort_order, 99) AS sort_order,
|
||||
COUNT(*)::int AS count
|
||||
FROM issues i
|
||||
LEFT JOIN issue_statuses s ON s.schluessel = i.status
|
||||
GROUP BY
|
||||
COALESCE(s.schluessel, i.status),
|
||||
COALESCE(s.bezeichnung, i.status),
|
||||
COALESCE(s.farbe, 'default'),
|
||||
COALESCE(s.ist_abschluss, false),
|
||||
COALESCE(s.sort_order, 99)
|
||||
ORDER BY COALESCE(s.sort_order, 99)
|
||||
`);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.getIssueCounts failed', { error });
|
||||
throw new Error('Issue-Counts konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getIssueStatuses() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM issue_statuses ORDER BY sort_order ASC, id ASC`
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.getIssueStatuses failed', { error });
|
||||
throw new Error('Issue-Status konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function createIssueStatus(data: {
|
||||
schluessel: string;
|
||||
bezeichnung: string;
|
||||
farbe?: string;
|
||||
ist_abschluss?: boolean;
|
||||
ist_initial?: boolean;
|
||||
benoetigt_typ_freigabe?: boolean;
|
||||
sort_order?: number;
|
||||
}) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO issue_statuses (schluessel, bezeichnung, farbe, ist_abschluss, ist_initial, benoetigt_typ_freigabe, sort_order)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *`,
|
||||
[
|
||||
data.schluessel,
|
||||
data.bezeichnung,
|
||||
data.farbe ?? 'default',
|
||||
data.ist_abschluss ?? false,
|
||||
data.ist_initial ?? false,
|
||||
data.benoetigt_typ_freigabe ?? false,
|
||||
data.sort_order ?? 0,
|
||||
]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('IssueService.createIssueStatus failed', { error });
|
||||
throw new Error('Issue-Status konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateIssueStatus(id: number, data: {
|
||||
bezeichnung?: string;
|
||||
farbe?: string;
|
||||
ist_abschluss?: boolean;
|
||||
ist_initial?: boolean;
|
||||
benoetigt_typ_freigabe?: boolean;
|
||||
sort_order?: number;
|
||||
aktiv?: boolean;
|
||||
}) {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.bezeichnung !== undefined) { setClauses.push(`bezeichnung = $${idx}`); values.push(data.bezeichnung); idx++; }
|
||||
if (data.farbe !== undefined) { setClauses.push(`farbe = $${idx}`); values.push(data.farbe); idx++; }
|
||||
if (data.ist_abschluss !== undefined) { setClauses.push(`ist_abschluss = $${idx}`); values.push(data.ist_abschluss); idx++; }
|
||||
if (data.ist_initial !== undefined) { setClauses.push(`ist_initial = $${idx}`); values.push(data.ist_initial); idx++; }
|
||||
if (data.benoetigt_typ_freigabe !== undefined) { setClauses.push(`benoetigt_typ_freigabe = $${idx}`); values.push(data.benoetigt_typ_freigabe); idx++; }
|
||||
if (data.sort_order !== undefined) { setClauses.push(`sort_order = $${idx}`); values.push(data.sort_order); idx++; }
|
||||
if (data.aktiv !== undefined) { setClauses.push(`aktiv = $${idx}`); values.push(data.aktiv); idx++; }
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
const r = await pool.query(`SELECT * FROM issue_statuses WHERE id = $1`, [id]);
|
||||
return r.rows[0] || null;
|
||||
}
|
||||
values.push(id);
|
||||
const result = await pool.query(
|
||||
`UPDATE issue_statuses SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.updateIssueStatus failed', { error, id });
|
||||
throw new Error('Issue-Status konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteIssueStatus(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE issue_statuses SET aktiv = false WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.deleteIssueStatus failed', { error, id });
|
||||
throw new Error('Issue-Status konnte nicht deaktiviert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getIssuePriorities() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`SELECT * FROM issue_prioritaeten ORDER BY sort_order ASC, id ASC`
|
||||
);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.getIssuePriorities failed', { error });
|
||||
throw new Error('Issue-Prioritäten konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function createIssuePriority(data: {
|
||||
schluessel: string;
|
||||
bezeichnung: string;
|
||||
farbe?: string;
|
||||
sort_order?: number;
|
||||
}) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`INSERT INTO issue_prioritaeten (schluessel, bezeichnung, farbe, sort_order)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *`,
|
||||
[data.schluessel, data.bezeichnung, data.farbe ?? '#9e9e9e', data.sort_order ?? 0]
|
||||
);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error('IssueService.createIssuePriority failed', { error });
|
||||
throw new Error('Priorität konnte nicht erstellt werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateIssuePriority(id: number, data: {
|
||||
bezeichnung?: string;
|
||||
farbe?: string;
|
||||
sort_order?: number;
|
||||
aktiv?: boolean;
|
||||
}) {
|
||||
try {
|
||||
const setClauses: string[] = [];
|
||||
const values: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (data.bezeichnung !== undefined) { setClauses.push(`bezeichnung = $${idx}`); values.push(data.bezeichnung); idx++; }
|
||||
if (data.farbe !== undefined) { setClauses.push(`farbe = $${idx}`); values.push(data.farbe); idx++; }
|
||||
if (data.sort_order !== undefined) { setClauses.push(`sort_order = $${idx}`); values.push(data.sort_order); idx++; }
|
||||
if (data.aktiv !== undefined) { setClauses.push(`aktiv = $${idx}`); values.push(data.aktiv); idx++; }
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
const r = await pool.query(`SELECT * FROM issue_prioritaeten WHERE id = $1`, [id]);
|
||||
return r.rows[0] || null;
|
||||
}
|
||||
values.push(id);
|
||||
const result = await pool.query(
|
||||
`UPDATE issue_prioritaeten SET ${setClauses.join(', ')} WHERE id = $${idx} RETURNING *`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.updateIssuePriority failed', { error, id });
|
||||
throw new Error('Priorität konnte nicht aktualisiert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteIssuePriority(id: number) {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
`UPDATE issue_prioritaeten SET aktiv = false WHERE id = $1 RETURNING *`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
} catch (error) {
|
||||
logger.error('IssueService.deleteIssuePriority failed', { error, id });
|
||||
throw new Error('Priorität konnte nicht deaktiviert werden');
|
||||
}
|
||||
}
|
||||
|
||||
async function getStatusmeldungen() {
|
||||
try {
|
||||
const result = await pool.query(
|
||||
@@ -465,6 +647,14 @@ export default {
|
||||
deactivateType,
|
||||
getAssignableMembers,
|
||||
getIssueCounts,
|
||||
getIssueStatuses,
|
||||
createIssueStatus,
|
||||
updateIssueStatus,
|
||||
deleteIssueStatus,
|
||||
getIssuePriorities,
|
||||
createIssuePriority,
|
||||
updateIssuePriority,
|
||||
deleteIssuePriority,
|
||||
getStatusmeldungen,
|
||||
createStatusmeldung,
|
||||
updateStatusmeldung,
|
||||
|
||||
Reference in New Issue
Block a user