Initial commit: Helpdesk application setup
- Backend: Node.js/TypeScript with Prisma ORM - Frontend: Vite + TypeScript - Project configuration and documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
272
backend/src/controllers/users.controller.ts
Normal file
272
backend/src/controllers/users.controller.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { Response } from 'express';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import prisma from '../config/database';
|
||||
import { successResponse, errorResponse, paginatedResponse, parseQueryInt, getParam, getQueryString } from '../utils/helpers';
|
||||
import { AuthRequest } from '../middleware/auth.middleware';
|
||||
|
||||
// Jednoduchý zoznam aktívnych používateľov (pre select/dropdown)
|
||||
// Podporuje server-side vyhľadávanie pre lepší výkon pri veľkom počte používateľov
|
||||
export const getUsersSimple = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const search = getQueryString(req, 'search');
|
||||
const limit = parseQueryInt(req.query.limit, 50); // Default 50, max 100
|
||||
const actualLimit = Math.min(limit, 100);
|
||||
|
||||
const where = {
|
||||
active: true,
|
||||
...(search && {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: 'insensitive' as const } },
|
||||
{ email: { contains: search, mode: 'insensitive' as const } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
where,
|
||||
take: actualLimit,
|
||||
orderBy: { name: 'asc' },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
email: true,
|
||||
},
|
||||
});
|
||||
|
||||
successResponse(res, users);
|
||||
} catch (error) {
|
||||
console.error('Error fetching users simple:', error);
|
||||
errorResponse(res, 'Chyba pri načítaní používateľov.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
export const getUsers = 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 active = getQueryString(req, 'active');
|
||||
const roleId = getQueryString(req, 'roleId');
|
||||
|
||||
const where = {
|
||||
...(active !== undefined && { active: active === 'true' }),
|
||||
...(roleId && { roleId }),
|
||||
...(search && {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: 'insensitive' as const } },
|
||||
{ email: { contains: search, mode: 'insensitive' as const } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
const [users, total] = await Promise.all([
|
||||
prisma.user.findMany({
|
||||
where,
|
||||
skip,
|
||||
take: limit,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
active: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.user.count({ where }),
|
||||
]);
|
||||
|
||||
paginatedResponse(res, users, total, page, limit);
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
errorResponse(res, 'Chyba pri načítaní používateľov.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
export const getUser = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const id = getParam(req, 'id');
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
active: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
permissions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
errorResponse(res, 'Používateľ nebol nájdený.', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
successResponse(res, user);
|
||||
} catch (error) {
|
||||
console.error('Error fetching user:', error);
|
||||
errorResponse(res, 'Chyba pri načítaní používateľa.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUser = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const id = getParam(req, 'id');
|
||||
const { email, name, active, password } = req.body;
|
||||
|
||||
const existingUser = await prisma.user.findUnique({ where: { id } });
|
||||
|
||||
if (!existingUser) {
|
||||
errorResponse(res, 'Používateľ nebol nájdený.', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check email uniqueness if changing
|
||||
if (email && email !== existingUser.email) {
|
||||
const emailExists = await prisma.user.findUnique({ where: { email } });
|
||||
if (emailExists) {
|
||||
errorResponse(res, 'Email je už používaný.', 409);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const updateData: Record<string, unknown> = {};
|
||||
if (email) updateData.email = email;
|
||||
if (name) updateData.name = name;
|
||||
if (active !== undefined) updateData.active = active;
|
||||
if (password) updateData.password = await bcrypt.hash(password, 10);
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
active: true,
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (req.logActivity) {
|
||||
await req.logActivity('UPDATE', 'User', id, updateData);
|
||||
}
|
||||
|
||||
successResponse(res, user, 'Používateľ bol aktualizovaný.');
|
||||
} catch (error) {
|
||||
console.error('Error updating user:', error);
|
||||
errorResponse(res, 'Chyba pri aktualizácii používateľa.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteUser = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const id = getParam(req, 'id');
|
||||
|
||||
// Prevent self-deletion
|
||||
if (req.user?.userId === id) {
|
||||
errorResponse(res, 'Nemôžete vymazať vlastný účet.', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id } });
|
||||
|
||||
if (!user) {
|
||||
errorResponse(res, 'Používateľ nebol nájdený.', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Soft delete - just deactivate
|
||||
await prisma.user.update({
|
||||
where: { id },
|
||||
data: { active: false },
|
||||
});
|
||||
|
||||
if (req.logActivity) {
|
||||
await req.logActivity('DELETE', 'User', id);
|
||||
}
|
||||
|
||||
successResponse(res, null, 'Používateľ bol deaktivovaný.');
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
errorResponse(res, 'Chyba pri mazaní používateľa.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUserRole = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const id = getParam(req, 'id');
|
||||
const { roleId } = req.body;
|
||||
|
||||
// Prevent changing own role
|
||||
if (req.user?.userId === id) {
|
||||
errorResponse(res, 'Nemôžete zmeniť vlastnú rolu.', 400);
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id } });
|
||||
|
||||
if (!user) {
|
||||
errorResponse(res, 'Používateľ nebol nájdený.', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const role = await prisma.userRole.findUnique({ where: { id: roleId } });
|
||||
|
||||
if (!role) {
|
||||
errorResponse(res, 'Rola neexistuje.', 404);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id },
|
||||
data: { roleId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
role: {
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (req.logActivity) {
|
||||
await req.logActivity('UPDATE', 'User', id, { roleId, roleName: role.name });
|
||||
}
|
||||
|
||||
successResponse(res, updatedUser, 'Rola používateľa bola zmenená.');
|
||||
} catch (error) {
|
||||
console.error('Error updating user role:', error);
|
||||
errorResponse(res, 'Chyba pri zmene roly.', 500);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user