feat: AI Kurzy module, project/service documents, services SQL import
- Add AI Kurzy module with courses, participants, and registrations management - Add project documents and service documents features - Add service folders for document organization - Add SQL import queries for services from firmy.slovensko.ai - Update todo notifications and group messaging - Various API improvements and bug fixes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
243
src/controllers/ai-kurzy.controller.js
Normal file
243
src/controllers/ai-kurzy.controller.js
Normal file
@@ -0,0 +1,243 @@
|
||||
import * as aiKurzyService from '../services/ai-kurzy.service.js';
|
||||
|
||||
// ==================== KURZY ====================
|
||||
|
||||
export const getAllKurzy = async (req, res, next) => {
|
||||
try {
|
||||
const kurzy = await aiKurzyService.getAllKurzy();
|
||||
res.json({ data: kurzy });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getKurzById = async (req, res, next) => {
|
||||
try {
|
||||
const { kurzId } = req.params;
|
||||
const kurz = await aiKurzyService.getKurzById(parseInt(kurzId));
|
||||
res.json({ data: kurz });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createKurz = async (req, res, next) => {
|
||||
try {
|
||||
const kurz = await aiKurzyService.createKurz(req.body);
|
||||
res.status(201).json({ data: kurz });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateKurz = async (req, res, next) => {
|
||||
try {
|
||||
const { kurzId } = req.params;
|
||||
const kurz = await aiKurzyService.updateKurz(parseInt(kurzId), req.body);
|
||||
res.json({ data: kurz });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteKurz = async (req, res, next) => {
|
||||
try {
|
||||
const { kurzId } = req.params;
|
||||
const result = await aiKurzyService.deleteKurz(parseInt(kurzId));
|
||||
res.json({ data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== UCASTNICI ====================
|
||||
|
||||
export const getAllUcastnici = async (req, res, next) => {
|
||||
try {
|
||||
const ucastnici = await aiKurzyService.getAllUcastnici();
|
||||
res.json({ data: ucastnici });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getUcastnikById = async (req, res, next) => {
|
||||
try {
|
||||
const { ucastnikId } = req.params;
|
||||
const ucastnik = await aiKurzyService.getUcastnikById(parseInt(ucastnikId));
|
||||
res.json({ data: ucastnik });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createUcastnik = async (req, res, next) => {
|
||||
try {
|
||||
const ucastnik = await aiKurzyService.createUcastnik(req.body);
|
||||
res.status(201).json({ data: ucastnik });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUcastnik = async (req, res, next) => {
|
||||
try {
|
||||
const { ucastnikId } = req.params;
|
||||
const ucastnik = await aiKurzyService.updateUcastnik(parseInt(ucastnikId), req.body);
|
||||
res.json({ data: ucastnik });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteUcastnik = async (req, res, next) => {
|
||||
try {
|
||||
const { ucastnikId } = req.params;
|
||||
const result = await aiKurzyService.deleteUcastnik(parseInt(ucastnikId));
|
||||
res.json({ data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== REGISTRACIE ====================
|
||||
|
||||
export const getAllRegistracie = async (req, res, next) => {
|
||||
try {
|
||||
const { kurzId } = req.query;
|
||||
const registracie = await aiKurzyService.getAllRegistracie(kurzId ? parseInt(kurzId) : null);
|
||||
res.json({ data: registracie });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getRegistraciaById = async (req, res, next) => {
|
||||
try {
|
||||
const { registraciaId } = req.params;
|
||||
const registracia = await aiKurzyService.getRegistraciaById(parseInt(registraciaId));
|
||||
res.json({ data: registracia });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createRegistracia = async (req, res, next) => {
|
||||
try {
|
||||
const registracia = await aiKurzyService.createRegistracia(req.body);
|
||||
res.status(201).json({ data: registracia });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateRegistracia = async (req, res, next) => {
|
||||
try {
|
||||
const { registraciaId } = req.params;
|
||||
const registracia = await aiKurzyService.updateRegistracia(parseInt(registraciaId), req.body);
|
||||
res.json({ data: registracia });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRegistracia = async (req, res, next) => {
|
||||
try {
|
||||
const { registraciaId } = req.params;
|
||||
const result = await aiKurzyService.deleteRegistracia(parseInt(registraciaId));
|
||||
res.json({ data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== COMBINED TABLE (Excel-style) ====================
|
||||
|
||||
export const getCombinedTable = async (req, res, next) => {
|
||||
try {
|
||||
const data = await aiKurzyService.getCombinedTableData();
|
||||
res.json({ data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateField = async (req, res, next) => {
|
||||
try {
|
||||
const { registraciaId } = req.params;
|
||||
const { field, value } = req.body;
|
||||
const result = await aiKurzyService.updateField(parseInt(registraciaId), field, value);
|
||||
res.json({ data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== PRILOHY (Documents) ====================
|
||||
|
||||
export const getPrilohy = async (req, res, next) => {
|
||||
try {
|
||||
const { registraciaId } = req.params;
|
||||
const prilohy = await aiKurzyService.getPrilohyByRegistracia(parseInt(registraciaId));
|
||||
res.json({ data: prilohy });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadPriloha = async (req, res, next) => {
|
||||
try {
|
||||
const { registraciaId } = req.params;
|
||||
const file = req.file;
|
||||
|
||||
if (!file) {
|
||||
return res.status(400).json({ message: 'Súbor je povinný' });
|
||||
}
|
||||
|
||||
const priloha = await aiKurzyService.createPriloha({
|
||||
registraciaId: parseInt(registraciaId),
|
||||
nazovSuboru: file.originalname,
|
||||
typPrilohy: req.body.typPrilohy || 'ine',
|
||||
cestaKSuboru: file.path,
|
||||
mimeType: file.mimetype,
|
||||
velkostSuboru: file.size,
|
||||
popis: req.body.popis || null,
|
||||
});
|
||||
|
||||
res.status(201).json({ data: priloha });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePriloha = async (req, res, next) => {
|
||||
try {
|
||||
const { prilohaId } = req.params;
|
||||
const result = await aiKurzyService.deletePriloha(parseInt(prilohaId));
|
||||
|
||||
// Optionally delete the file from disk
|
||||
if (result.filePath) {
|
||||
const fs = await import('fs/promises');
|
||||
try {
|
||||
await fs.unlink(result.filePath);
|
||||
} catch {
|
||||
// File might not exist, ignore
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ data: { success: true } });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== STATISTICS ====================
|
||||
|
||||
export const getStats = async (req, res, next) => {
|
||||
try {
|
||||
const stats = await aiKurzyService.getKurzyStats();
|
||||
res.json({ data: stats });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
88
src/controllers/project-document.controller.js
Normal file
88
src/controllers/project-document.controller.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as projectDocumentService from '../services/project-document.service.js';
|
||||
|
||||
/**
|
||||
* Get all documents for a project
|
||||
* GET /api/projects/:projectId/documents
|
||||
*/
|
||||
export const getDocuments = async (req, res, next) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
|
||||
const documents = await projectDocumentService.getDocumentsByProjectId(projectId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: documents.length,
|
||||
data: documents,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload a document for a project
|
||||
* POST /api/projects/:projectId/documents
|
||||
*/
|
||||
export const uploadDocument = async (req, res, next) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const userId = req.userId;
|
||||
const { description } = req.body;
|
||||
|
||||
const document = await projectDocumentService.uploadDocument({
|
||||
projectId,
|
||||
userId,
|
||||
file: req.file,
|
||||
description,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: document,
|
||||
message: 'Dokument bol nahraný',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Download a document
|
||||
* GET /api/projects/:projectId/documents/:docId/download
|
||||
*/
|
||||
export const downloadDocument = async (req, res, next) => {
|
||||
try {
|
||||
const { projectId, docId } = req.params;
|
||||
|
||||
const { filePath, fileName, fileType } = await projectDocumentService.getDocumentForDownload(
|
||||
projectId,
|
||||
docId
|
||||
);
|
||||
|
||||
res.setHeader('Content-Type', fileType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
|
||||
res.sendFile(filePath);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a document
|
||||
* DELETE /api/projects/:projectId/documents/:docId
|
||||
*/
|
||||
export const deleteDocument = async (req, res, next) => {
|
||||
try {
|
||||
const { projectId, docId } = req.params;
|
||||
|
||||
const result = await projectDocumentService.deleteDocument(projectId, docId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
54
src/controllers/service-document.controller.js
Normal file
54
src/controllers/service-document.controller.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as serviceDocumentService from '../services/service-document.service.js';
|
||||
|
||||
export const getDocumentsByFolderId = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId } = req.params;
|
||||
const documents = await serviceDocumentService.getDocumentsByFolderId(folderId);
|
||||
res.json({ data: documents });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const uploadDocument = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId } = req.params;
|
||||
const userId = req.user.id;
|
||||
const file = req.file;
|
||||
const { description } = req.body;
|
||||
|
||||
const document = await serviceDocumentService.uploadDocument({
|
||||
folderId,
|
||||
userId,
|
||||
file,
|
||||
description,
|
||||
});
|
||||
|
||||
res.status(201).json({ data: document });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadDocument = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId, documentId } = req.params;
|
||||
const { filePath, fileName, fileType } = await serviceDocumentService.getDocumentForDownload(folderId, documentId);
|
||||
|
||||
res.setHeader('Content-Type', fileType);
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`);
|
||||
res.sendFile(filePath);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteDocument = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId, documentId } = req.params;
|
||||
const result = await serviceDocumentService.deleteDocument(folderId, documentId);
|
||||
res.json({ data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
52
src/controllers/service-folder.controller.js
Normal file
52
src/controllers/service-folder.controller.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as serviceFolderService from '../services/service-folder.service.js';
|
||||
|
||||
export const getAllFolders = async (req, res, next) => {
|
||||
try {
|
||||
const folders = await serviceFolderService.getAllFolders();
|
||||
res.json({ data: folders });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getFolderById = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId } = req.params;
|
||||
const folder = await serviceFolderService.getFolderById(folderId);
|
||||
res.json({ data: folder });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const createFolder = async (req, res, next) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
const userId = req.user.id;
|
||||
const folder = await serviceFolderService.createFolder({ name, userId });
|
||||
res.status(201).json({ data: folder });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateFolder = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId } = req.params;
|
||||
const { name } = req.body;
|
||||
const folder = await serviceFolderService.updateFolder(folderId, { name });
|
||||
res.json({ data: folder });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteFolder = async (req, res, next) => {
|
||||
try {
|
||||
const { folderId } = req.params;
|
||||
const result = await serviceFolderService.deleteFolder(folderId);
|
||||
res.json({ data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
@@ -231,3 +231,81 @@ export const toggleTodo = async (req, res, next) => {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get overdue todos count
|
||||
* GET /api/todos/overdue-count
|
||||
*/
|
||||
export const getOverdueCount = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const userRole = req.user?.role;
|
||||
|
||||
const count = await todoService.getOverdueCount(userId, userRole);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: { overdueCount: count },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get completed todos created by current user count
|
||||
* GET /api/todos/completed-by-me
|
||||
*/
|
||||
export const getCompletedByMeCount = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
|
||||
const count = await todoService.getCompletedByMeCount(userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: { completedByMeCount: count },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get combined todo counts for sidebar badges
|
||||
* GET /api/todos/counts
|
||||
*/
|
||||
export const getTodoCounts = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
const userRole = req.user?.role;
|
||||
|
||||
const counts = await todoService.getTodoCounts(userId, userRole);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: counts,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all completed todos created by the user as notified
|
||||
* POST /api/todos/mark-completed-notified
|
||||
*/
|
||||
export const markCompletedAsNotified = async (req, res, next) => {
|
||||
try {
|
||||
const userId = req.userId;
|
||||
|
||||
await todoService.markCompletedAsNotified(userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'Completed todos marked as notified',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user