- Backend: Node.js/TypeScript with Prisma ORM - Frontend: Vite + TypeScript - Project configuration and documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
289 lines
8.7 KiB
TypeScript
289 lines
8.7 KiB
TypeScript
import { Response } from 'express';
|
|
import prisma from '../config/database';
|
|
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
|
|
import { AuthRequest } from '../middleware/auth.middleware';
|
|
import { configService } from '../services/config.service';
|
|
|
|
export const getProjects = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const page = parseQueryInt(req.query.page, 1);
|
|
const limit = parseQueryInt(req.query.limit, 20);
|
|
const skip = (page - 1) * limit;
|
|
const search = getQueryString(req, 'search');
|
|
const statusId = getQueryString(req, 'statusId');
|
|
const customerId = getQueryString(req, 'customerId');
|
|
const ownerId = getQueryString(req, 'ownerId');
|
|
|
|
const where = {
|
|
...(statusId && { statusId }),
|
|
...(customerId && { customerId }),
|
|
...(ownerId && { ownerId }),
|
|
...(search && {
|
|
OR: [
|
|
{ name: { contains: search, mode: 'insensitive' as const } },
|
|
{ description: { contains: search, mode: 'insensitive' as const } },
|
|
],
|
|
}),
|
|
};
|
|
|
|
const [projects, total] = await Promise.all([
|
|
prisma.project.findMany({
|
|
where,
|
|
skip,
|
|
take: limit,
|
|
orderBy: { createdAt: 'desc' },
|
|
include: {
|
|
status: true,
|
|
customer: { select: { id: true, name: true } },
|
|
owner: { select: { id: true, name: true, email: true } },
|
|
_count: { select: { tasks: true, members: true } },
|
|
},
|
|
}),
|
|
prisma.project.count({ where }),
|
|
]);
|
|
|
|
paginatedResponse(res, projects, total, page, limit);
|
|
} catch (error) {
|
|
console.error('Error fetching projects:', error);
|
|
errorResponse(res, 'Chyba pri načítaní projektov.', 500);
|
|
}
|
|
};
|
|
|
|
export const getProject = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
|
|
const project = await prisma.project.findUnique({
|
|
where: { id },
|
|
include: {
|
|
status: true,
|
|
customer: true,
|
|
owner: { select: { id: true, name: true, email: true } },
|
|
members: {
|
|
include: {
|
|
user: { select: { id: true, name: true, email: true } },
|
|
},
|
|
},
|
|
tags: { include: { tag: true } },
|
|
_count: { select: { tasks: true } },
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
errorResponse(res, 'Projekt nebol nájdený.', 404);
|
|
return;
|
|
}
|
|
|
|
successResponse(res, project);
|
|
} catch (error) {
|
|
console.error('Error fetching project:', error);
|
|
errorResponse(res, 'Chyba pri načítaní projektu.', 500);
|
|
}
|
|
};
|
|
|
|
export const createProject = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
let { statusId } = req.body;
|
|
|
|
// Get initial status if not provided
|
|
if (!statusId) {
|
|
const initialStatus = await configService.getInitialTaskStatus();
|
|
if (initialStatus) {
|
|
statusId = initialStatus.id;
|
|
} else {
|
|
// Fallback: vezmi prvý aktívny status
|
|
const allStatuses = await configService.getTaskStatuses();
|
|
const statuses = allStatuses as { id: string }[];
|
|
if (statuses.length > 0) {
|
|
statusId = statuses[0].id;
|
|
} else {
|
|
errorResponse(res, 'Žiadny status nie je nakonfigurovaný. Spustite seed.', 500);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const project = await prisma.project.create({
|
|
data: {
|
|
name: req.body.name,
|
|
description: req.body.description,
|
|
customerId: req.body.customerId || null,
|
|
ownerId: req.user!.userId,
|
|
statusId,
|
|
softDeadline: req.body.softDeadline ? new Date(req.body.softDeadline) : null,
|
|
hardDeadline: req.body.hardDeadline ? new Date(req.body.hardDeadline) : null,
|
|
},
|
|
include: {
|
|
status: true,
|
|
customer: { select: { id: true, name: true } },
|
|
owner: { select: { id: true, name: true } },
|
|
},
|
|
});
|
|
|
|
if (req.logActivity) {
|
|
await req.logActivity('CREATE', 'Project', project.id, { name: project.name });
|
|
}
|
|
|
|
successResponse(res, project, 'Projekt bol vytvorený.', 201);
|
|
} catch (error) {
|
|
console.error('Error creating project:', error);
|
|
errorResponse(res, 'Chyba pri vytváraní projektu.', 500);
|
|
}
|
|
};
|
|
|
|
export const updateProject = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
|
|
const existing = await prisma.project.findUnique({ where: { id } });
|
|
|
|
if (!existing) {
|
|
errorResponse(res, 'Projekt nebol nájdený.', 404);
|
|
return;
|
|
}
|
|
|
|
const updateData: Record<string, unknown> = {};
|
|
if (req.body.name) updateData.name = req.body.name;
|
|
if (req.body.description !== undefined) updateData.description = req.body.description;
|
|
if (req.body.customerId !== undefined) updateData.customerId = req.body.customerId || null;
|
|
if (req.body.statusId) updateData.statusId = req.body.statusId;
|
|
if (req.body.softDeadline !== undefined) {
|
|
updateData.softDeadline = req.body.softDeadline ? new Date(req.body.softDeadline) : null;
|
|
}
|
|
if (req.body.hardDeadline !== undefined) {
|
|
updateData.hardDeadline = req.body.hardDeadline ? new Date(req.body.hardDeadline) : null;
|
|
}
|
|
|
|
const project = await prisma.project.update({
|
|
where: { id },
|
|
data: updateData,
|
|
include: {
|
|
status: true,
|
|
customer: { select: { id: true, name: true } },
|
|
owner: { select: { id: true, name: true } },
|
|
},
|
|
});
|
|
|
|
if (req.logActivity) {
|
|
await req.logActivity('UPDATE', 'Project', id, updateData);
|
|
}
|
|
|
|
successResponse(res, project, 'Projekt bol aktualizovaný.');
|
|
} catch (error) {
|
|
console.error('Error updating project:', error);
|
|
errorResponse(res, 'Chyba pri aktualizácii projektu.', 500);
|
|
}
|
|
};
|
|
|
|
export const deleteProject = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
|
|
const project = await prisma.project.findUnique({ where: { id } });
|
|
|
|
if (!project) {
|
|
errorResponse(res, 'Projekt nebol nájdený.', 404);
|
|
return;
|
|
}
|
|
|
|
await prisma.project.delete({ where: { id } });
|
|
|
|
if (req.logActivity) {
|
|
await req.logActivity('DELETE', 'Project', id);
|
|
}
|
|
|
|
successResponse(res, null, 'Projekt bol vymazaný.');
|
|
} catch (error) {
|
|
console.error('Error deleting project:', error);
|
|
errorResponse(res, 'Chyba pri mazaní projektu.', 500);
|
|
}
|
|
};
|
|
|
|
export const updateProjectStatus = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
const { statusId } = req.body;
|
|
|
|
const project = await prisma.project.update({
|
|
where: { id },
|
|
data: { statusId },
|
|
include: { status: true },
|
|
});
|
|
|
|
if (req.logActivity) {
|
|
await req.logActivity('STATUS_CHANGE', 'Project', id, { statusId });
|
|
}
|
|
|
|
successResponse(res, project, 'Status projektu bol zmenený.');
|
|
} catch (error) {
|
|
console.error('Error updating project status:', error);
|
|
errorResponse(res, 'Chyba pri zmene statusu.', 500);
|
|
}
|
|
};
|
|
|
|
export const getProjectTasks = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
|
|
const tasks = await prisma.task.findMany({
|
|
where: { projectId: id },
|
|
include: {
|
|
status: true,
|
|
priority: true,
|
|
assignees: {
|
|
include: { user: { select: { id: true, name: true } } },
|
|
},
|
|
},
|
|
orderBy: [{ priority: { level: 'desc' } }, { createdAt: 'desc' }],
|
|
});
|
|
|
|
successResponse(res, tasks);
|
|
} catch (error) {
|
|
console.error('Error fetching project tasks:', error);
|
|
errorResponse(res, 'Chyba pri načítaní úloh.', 500);
|
|
}
|
|
};
|
|
|
|
export const addProjectMember = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
const { userId } = req.body;
|
|
|
|
const member = await prisma.projectMember.create({
|
|
data: {
|
|
projectId: id,
|
|
userId,
|
|
},
|
|
include: {
|
|
user: { select: { id: true, name: true, email: true } },
|
|
},
|
|
});
|
|
|
|
successResponse(res, member, 'Člen bol pridaný.', 201);
|
|
} catch (error) {
|
|
console.error('Error adding project member:', error);
|
|
errorResponse(res, 'Chyba pri pridávaní člena.', 500);
|
|
}
|
|
};
|
|
|
|
export const removeProjectMember = async (req: AuthRequest, res: Response): Promise<void> => {
|
|
try {
|
|
const id = getParam(req, 'id');
|
|
const userId = getParam(req, 'userId');
|
|
|
|
await prisma.projectMember.delete({
|
|
where: {
|
|
projectId_userId: {
|
|
projectId: id,
|
|
userId,
|
|
},
|
|
},
|
|
});
|
|
|
|
successResponse(res, null, 'Člen bol odstránený.');
|
|
} catch (error) {
|
|
console.error('Error removing project member:', error);
|
|
errorResponse(res, 'Chyba pri odstraňovaní člena.', 500);
|
|
}
|
|
};
|