feat: Add creator info, team management for companies, and member access control
- Add creator info (username) to companies, projects, and notes responses - Add company_users table for team management on companies - Add resourceAccessMiddleware for member access control - Members can only see resources they are directly assigned to - Companies, projects, and todos are now filtered by user assignments - Add personal contacts feature 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,12 +7,15 @@ import { logCompanyCreated, logCompanyDeleted } from '../services/audit.service.
|
||||
/**
|
||||
* Get all companies
|
||||
* GET /api/companies?search=query
|
||||
* Members only see companies they are assigned to
|
||||
*/
|
||||
export const getAllCompanies = async (req, res, next) => {
|
||||
try {
|
||||
const { search } = req.query;
|
||||
const userId = req.user?.id;
|
||||
const userRole = req.user?.role;
|
||||
|
||||
const companies = await companyService.getAllCompanies(search);
|
||||
const companies = await companyService.getAllCompanies(search, userId, userRole);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
@@ -333,9 +336,11 @@ export const deleteCompanyReminder = async (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getReminderSummary = async (_req, res, next) => {
|
||||
export const getReminderSummary = async (req, res, next) => {
|
||||
try {
|
||||
const summary = await companyReminderService.getReminderSummary();
|
||||
const userId = req.user?.id;
|
||||
const userRole = req.user?.role;
|
||||
const summary = await companyReminderService.getReminderSummary(userId, userRole);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: summary,
|
||||
@@ -345,9 +350,11 @@ export const getReminderSummary = async (_req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getReminderCountsByCompany = async (_req, res, next) => {
|
||||
export const getReminderCountsByCompany = async (req, res, next) => {
|
||||
try {
|
||||
const counts = await companyReminderService.getReminderCountsByCompany();
|
||||
const userId = req.user?.id;
|
||||
const userRole = req.user?.role;
|
||||
const counts = await companyReminderService.getReminderCountsByCompany(userId, userRole);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: counts,
|
||||
@@ -357,9 +364,11 @@ export const getReminderCountsByCompany = async (_req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getUpcomingReminders = async (_req, res, next) => {
|
||||
export const getUpcomingReminders = async (req, res, next) => {
|
||||
try {
|
||||
const reminders = await companyReminderService.getUpcomingReminders();
|
||||
const userId = req.user?.id;
|
||||
const userRole = req.user?.role;
|
||||
const reminders = await companyReminderService.getUpcomingReminders(userId, userRole);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: reminders.length,
|
||||
@@ -369,3 +378,87 @@ export const getUpcomingReminders = async (_req, res, next) => {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get company users (team members)
|
||||
* GET /api/companies/:companyId/users
|
||||
*/
|
||||
export const getCompanyUsers = async (req, res, next) => {
|
||||
try {
|
||||
const { companyId } = req.params;
|
||||
|
||||
const users = await companyService.getCompanyUsers(companyId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
count: users.length,
|
||||
data: users,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign user to company
|
||||
* POST /api/companies/:companyId/users
|
||||
* Body: { userId, role }
|
||||
*/
|
||||
export const assignUserToCompany = async (req, res, next) => {
|
||||
try {
|
||||
const currentUserId = req.userId;
|
||||
const { companyId } = req.params;
|
||||
const { userId, role } = req.body;
|
||||
|
||||
const assignment = await companyService.assignUserToCompany(companyId, userId, currentUserId, role);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: assignment,
|
||||
message: 'Používateľ bol priradený k firme',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove user from company
|
||||
* DELETE /api/companies/:companyId/users/:userId
|
||||
*/
|
||||
export const removeUserFromCompany = async (req, res, next) => {
|
||||
try {
|
||||
const { companyId, userId } = req.params;
|
||||
|
||||
const result = await companyService.removeUserFromCompany(companyId, userId);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: result.message,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user role on company
|
||||
* PATCH /api/companies/:companyId/users/:userId
|
||||
* Body: { role }
|
||||
*/
|
||||
export const updateUserRoleOnCompany = async (req, res, next) => {
|
||||
try {
|
||||
const { companyId, userId } = req.params;
|
||||
const { role } = req.body;
|
||||
|
||||
const assignment = await companyService.updateUserRoleOnCompany(companyId, userId, role);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: assignment,
|
||||
message: 'Rola používateľa bola aktualizovaná',
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,12 +5,15 @@ import { logProjectCreated, logProjectDeleted } from '../services/audit.service.
|
||||
/**
|
||||
* Get all projects
|
||||
* GET /api/projects?search=query&companyId=xxx
|
||||
* Members only see projects they are assigned to or projects of companies they are assigned to
|
||||
*/
|
||||
export const getAllProjects = async (req, res, next) => {
|
||||
try {
|
||||
const { search, companyId } = req.query;
|
||||
const userId = req.user?.id;
|
||||
const userRole = req.user?.role;
|
||||
|
||||
const projects = await projectService.getAllProjects(search, companyId);
|
||||
const projects = await projectService.getAllProjects(search, companyId, userId, userRole);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
|
||||
@@ -4,10 +4,13 @@ import { logTodoCreated, logTodoDeleted, logTodoCompleted } from '../services/au
|
||||
/**
|
||||
* Get all todos
|
||||
* GET /api/todos?search=query&projectId=xxx&companyId=xxx&assignedTo=xxx&status=xxx
|
||||
* Members only see todos they are assigned to
|
||||
*/
|
||||
export const getAllTodos = async (req, res, next) => {
|
||||
try {
|
||||
const { search, projectId, companyId, assignedTo, status, completed, priority } = req.query;
|
||||
const userId = req.user?.id;
|
||||
const userRole = req.user?.role;
|
||||
|
||||
// Handle both 'status' and 'completed' query params
|
||||
let statusFilter = status;
|
||||
@@ -24,7 +27,7 @@ export const getAllTodos = async (req, res, next) => {
|
||||
priority,
|
||||
};
|
||||
|
||||
const todos = await todoService.getAllTodos(filters);
|
||||
const todos = await todoService.getAllTodos(filters, userId, userRole);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
|
||||
@@ -158,6 +158,18 @@ export const companyReminders = pgTable('company_remind', {
|
||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
||||
});
|
||||
|
||||
// Company Users - many-to-many medzi companies a users (tím firmy)
|
||||
export const companyUsers = pgTable('company_users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
companyId: uuid('company_id').references(() => companies.id, { onDelete: 'cascade' }).notNull(),
|
||||
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
|
||||
role: text('role'), // napr. 'lead', 'member', 'viewer' - voliteľné
|
||||
addedBy: uuid('added_by').references(() => users.id, { onDelete: 'set null' }), // kto pridal používateľa do firmy
|
||||
addedAt: timestamp('added_at').defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
companyUserUnique: unique('company_user_unique').on(table.companyId, table.userId),
|
||||
}));
|
||||
|
||||
// Project Users - many-to-many medzi projects a users (tím projektu)
|
||||
export const projectUsers = pgTable('project_users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
|
||||
160
src/middlewares/auth/resourceAccessMiddleware.js
Normal file
160
src/middlewares/auth/resourceAccessMiddleware.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import { db } from '../../config/database.js';
|
||||
import { companyUsers, projectUsers, todoUsers } from '../../db/schema.js';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
|
||||
/**
|
||||
* Univerzálny middleware pre kontrolu prístupu k resources
|
||||
* Admin má prístup vždy, member len ak je priradený k resource
|
||||
*
|
||||
* Použitie:
|
||||
* checkResourceAccess('company', 'companyId') // pre /companies/:companyId
|
||||
* checkResourceAccess('project', 'projectId') // pre /projects/:projectId
|
||||
* checkResourceAccess('todo', 'todoId') // pre /todos/:todoId
|
||||
*/
|
||||
|
||||
// Mapovanie resource typu na junction tabuľku a stĺpce
|
||||
const resourceConfig = {
|
||||
company: {
|
||||
table: companyUsers,
|
||||
resourceIdColumn: 'companyId',
|
||||
},
|
||||
project: {
|
||||
table: projectUsers,
|
||||
resourceIdColumn: 'projectId',
|
||||
},
|
||||
todo: {
|
||||
table: todoUsers,
|
||||
resourceIdColumn: 'todoId',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Skontroluje či user má prístup k danému resource
|
||||
* @param {string} resourceType - Typ resource ('company', 'project', atď.)
|
||||
* @param {string} userId - ID používateľa
|
||||
* @param {string} resourceId - ID resource
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const hasAccessToResource = async (resourceType, userId, resourceId) => {
|
||||
const config = resourceConfig[resourceType];
|
||||
if (!config) {
|
||||
throw new Error(`Unknown resource type: ${resourceType}`);
|
||||
}
|
||||
|
||||
const { table, resourceIdColumn } = config;
|
||||
|
||||
const [assignment] = await db
|
||||
.select()
|
||||
.from(table)
|
||||
.where(and(
|
||||
eq(table[resourceIdColumn], resourceId),
|
||||
eq(table.userId, userId)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
return !!assignment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware factory pre kontrolu prístupu k resource
|
||||
* @param {string} resourceType - Typ resource ('company', 'project')
|
||||
* @param {string} paramName - Názov parametra v URL (napr. 'companyId', 'projectId')
|
||||
*/
|
||||
export const checkResourceAccess = (resourceType, paramName) => {
|
||||
return async (req, res, next) => {
|
||||
// Skontroluj či je user autentifikovaný
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Musíte byť prihlásený',
|
||||
statusCode: 401,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Admin má prístup vždy
|
||||
if (req.user.role === 'admin') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const resourceId = req.params[paramName];
|
||||
if (!resourceId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: `Chýba parameter ${paramName}`,
|
||||
statusCode: 400,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const hasAccess = await hasAccessToResource(resourceType, req.user.id, resourceId);
|
||||
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Nemáte prístup k tomuto zdroju',
|
||||
statusCode: 403,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Resource access check error:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Chyba pri overovaní prístupu',
|
||||
statusCode: 500,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper funkcie pre bežné prípady
|
||||
*/
|
||||
export const checkCompanyAccess = checkResourceAccess('company', 'companyId');
|
||||
export const checkProjectAccess = checkResourceAccess('project', 'projectId');
|
||||
export const checkTodoAccess = checkResourceAccess('todo', 'todoId');
|
||||
|
||||
/**
|
||||
* Získa zoznam resource IDs ku ktorým má user prístup
|
||||
* Užitočné pre filtrovanie v service vrstvách
|
||||
* @param {string} resourceType - Typ resource ('company', 'project')
|
||||
* @param {string} userId - ID používateľa
|
||||
* @returns {Promise<string[]>} - Zoznam resource IDs
|
||||
*/
|
||||
export const getAccessibleResourceIds = async (resourceType, userId) => {
|
||||
const config = resourceConfig[resourceType];
|
||||
if (!config) {
|
||||
throw new Error(`Unknown resource type: ${resourceType}`);
|
||||
}
|
||||
|
||||
const { table, resourceIdColumn } = config;
|
||||
|
||||
const assignments = await db
|
||||
.select({ resourceId: table[resourceIdColumn] })
|
||||
.from(table)
|
||||
.where(eq(table.userId, userId));
|
||||
|
||||
return assignments.map(a => a.resourceId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Skontroluje prístup a vráti boolean (pre použitie v services)
|
||||
* @param {string} resourceType
|
||||
* @param {string} userId
|
||||
* @param {string} resourceId
|
||||
* @param {string} userRole
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
export const canAccessResource = async (resourceType, userId, resourceId, userRole) => {
|
||||
if (userRole === 'admin') return true;
|
||||
return hasAccessToResource(resourceType, userId, resourceId);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import * as companyController from '../controllers/company.controller.js';
|
||||
import { authenticate } from '../middlewares/auth/authMiddleware.js';
|
||||
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
|
||||
import { checkCompanyAccess } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
|
||||
import { createCompanySchema, updateCompanySchema, createCompanyReminderSchema, updateCompanyReminderSchema } from '../validators/crm.validators.js';
|
||||
import { z } from 'zod';
|
||||
@@ -23,6 +24,7 @@ router.get('/email-unread', companyController.getCompanyUnreadCounts);
|
||||
router.get(
|
||||
'/:companyId/email-threads',
|
||||
validateParams(z.object({ companyId: z.string().uuid() })),
|
||||
checkCompanyAccess,
|
||||
companyController.getCompanyEmailThreads
|
||||
);
|
||||
|
||||
@@ -37,6 +39,7 @@ router.get('/', companyController.getAllCompanies);
|
||||
router.get(
|
||||
'/:companyId',
|
||||
validateParams(z.object({ companyId: z.string().uuid() })),
|
||||
checkCompanyAccess,
|
||||
companyController.getCompanyById
|
||||
);
|
||||
|
||||
@@ -69,6 +72,7 @@ router.delete(
|
||||
router.get(
|
||||
'/:companyId/notes',
|
||||
validateParams(z.object({ companyId: z.string().uuid() })),
|
||||
checkCompanyAccess,
|
||||
companyController.getCompanyNotes
|
||||
);
|
||||
|
||||
@@ -109,6 +113,7 @@ router.delete(
|
||||
router.get(
|
||||
'/:companyId/reminders',
|
||||
validateParams(z.object({ companyId: z.string().uuid() })),
|
||||
checkCompanyAccess,
|
||||
companyController.getCompanyReminders
|
||||
);
|
||||
|
||||
@@ -141,4 +146,46 @@ router.delete(
|
||||
companyController.deleteCompanyReminder
|
||||
);
|
||||
|
||||
// Company Users (Team Management)
|
||||
router.get(
|
||||
'/:companyId/users',
|
||||
validateParams(z.object({ companyId: z.string().uuid() })),
|
||||
checkCompanyAccess,
|
||||
companyController.getCompanyUsers
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:companyId/users',
|
||||
requireAdmin,
|
||||
validateParams(z.object({ companyId: z.string().uuid() })),
|
||||
validateBody(z.object({
|
||||
userId: z.string().uuid(),
|
||||
role: z.string().optional(),
|
||||
})),
|
||||
companyController.assignUserToCompany
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:companyId/users/:userId',
|
||||
requireAdmin,
|
||||
validateParams(z.object({
|
||||
companyId: z.string().uuid(),
|
||||
userId: z.string().uuid()
|
||||
})),
|
||||
validateBody(z.object({
|
||||
role: z.string().optional(),
|
||||
})),
|
||||
companyController.updateUserRoleOnCompany
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:companyId/users/:userId',
|
||||
requireAdmin,
|
||||
validateParams(z.object({
|
||||
companyId: z.string().uuid(),
|
||||
userId: z.string().uuid()
|
||||
})),
|
||||
companyController.removeUserFromCompany
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -2,6 +2,7 @@ import express from 'express';
|
||||
import * as projectController from '../controllers/project.controller.js';
|
||||
import { authenticate } from '../middlewares/auth/authMiddleware.js';
|
||||
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
|
||||
import { checkProjectAccess } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
|
||||
import { createProjectSchema, updateProjectSchema } from '../validators/crm.validators.js';
|
||||
import { z } from 'zod';
|
||||
@@ -22,6 +23,7 @@ router.get('/', projectController.getAllProjects);
|
||||
router.get(
|
||||
'/:projectId',
|
||||
validateParams(z.object({ projectId: z.string().uuid() })),
|
||||
checkProjectAccess,
|
||||
projectController.getProjectById
|
||||
);
|
||||
|
||||
@@ -54,6 +56,7 @@ router.delete(
|
||||
router.get(
|
||||
'/:projectId/notes',
|
||||
validateParams(z.object({ projectId: z.string().uuid() })),
|
||||
checkProjectAccess,
|
||||
projectController.getProjectNotes
|
||||
);
|
||||
|
||||
@@ -96,6 +99,7 @@ router.delete(
|
||||
router.get(
|
||||
'/:projectId/users',
|
||||
validateParams(z.object({ projectId: z.string().uuid() })),
|
||||
checkProjectAccess,
|
||||
projectController.getProjectUsers
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import express from 'express';
|
||||
import * as todoController from '../controllers/todo.controller.js';
|
||||
import { authenticate } from '../middlewares/auth/authMiddleware.js';
|
||||
import { requireAdmin } from '../middlewares/auth/roleMiddleware.js';
|
||||
import { checkTodoAccess } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
import { validateBody, validateParams } from '../middlewares/security/validateInput.js';
|
||||
import { createTodoSchema, updateTodoSchema } from '../validators/crm.validators.js';
|
||||
import { z } from 'zod';
|
||||
@@ -21,27 +23,31 @@ router.get('/', todoController.getAllTodos);
|
||||
router.get(
|
||||
'/:todoId',
|
||||
validateParams(z.object({ todoId: z.string().uuid() })),
|
||||
checkTodoAccess,
|
||||
todoController.getTodoById
|
||||
);
|
||||
|
||||
// Create new todo
|
||||
// Create new todo (admin only)
|
||||
router.post(
|
||||
'/',
|
||||
requireAdmin,
|
||||
validateBody(createTodoSchema),
|
||||
todoController.createTodo
|
||||
);
|
||||
|
||||
// Update todo
|
||||
// Update todo (admin only)
|
||||
router.patch(
|
||||
'/:todoId',
|
||||
requireAdmin,
|
||||
validateParams(z.object({ todoId: z.string().uuid() })),
|
||||
validateBody(updateTodoSchema),
|
||||
todoController.updateTodo
|
||||
);
|
||||
|
||||
// Delete todo
|
||||
// Delete todo (admin only)
|
||||
router.delete(
|
||||
'/:todoId',
|
||||
requireAdmin,
|
||||
validateParams(z.object({ todoId: z.string().uuid() })),
|
||||
todoController.deleteTodo
|
||||
);
|
||||
@@ -50,6 +56,7 @@ router.delete(
|
||||
router.patch(
|
||||
'/:todoId/toggle',
|
||||
validateParams(z.object({ todoId: z.string().uuid() })),
|
||||
checkTodoAccess,
|
||||
todoController.toggleTodo
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { companies, companyReminders } from '../db/schema.js';
|
||||
import { eq, desc, sql, and, lte, gte, isNull, or } from 'drizzle-orm';
|
||||
import { eq, desc, sql, and, lte, gte, isNull, or, inArray } from 'drizzle-orm';
|
||||
import { NotFoundError, BadRequestError } from '../utils/errors.js';
|
||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
|
||||
const ensureCompanyExists = async (companyId) => {
|
||||
const [company] = await db
|
||||
@@ -105,8 +106,17 @@ export const deleteReminder = async (companyId, reminderId) => {
|
||||
return { success: true, message: 'Reminder bol odstránený' };
|
||||
};
|
||||
|
||||
export const getReminderSummary = async () => {
|
||||
const [row] = await db
|
||||
export const getReminderSummary = async (userId = null, userRole = null) => {
|
||||
// Pre membera filtruj len pristupne firmy
|
||||
let accessibleCompanyIds = null;
|
||||
if (userRole && userRole !== 'admin' && userId) {
|
||||
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
||||
if (accessibleCompanyIds.length === 0) {
|
||||
return { total: 0, active: 0, completed: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
let query = db
|
||||
.select({
|
||||
total: sql`COUNT(*)::int`,
|
||||
active: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = false)::int`,
|
||||
@@ -114,6 +124,12 @@ export const getReminderSummary = async () => {
|
||||
})
|
||||
.from(companyReminders);
|
||||
|
||||
if (accessibleCompanyIds !== null) {
|
||||
query = query.where(inArray(companyReminders.companyId, accessibleCompanyIds));
|
||||
}
|
||||
|
||||
const [row] = await query;
|
||||
|
||||
return {
|
||||
total: row?.total ?? 0,
|
||||
active: row?.active ?? 0,
|
||||
@@ -121,15 +137,30 @@ export const getReminderSummary = async () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getReminderCountsByCompany = async () => {
|
||||
const rows = await db
|
||||
export const getReminderCountsByCompany = async (userId = null, userRole = null) => {
|
||||
// Pre membera filtruj len pristupne firmy
|
||||
let accessibleCompanyIds = null;
|
||||
if (userRole && userRole !== 'admin' && userId) {
|
||||
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
||||
if (accessibleCompanyIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let query = db
|
||||
.select({
|
||||
companyId: companyReminders.companyId,
|
||||
total: sql`COUNT(*)::int`,
|
||||
active: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = false)::int`,
|
||||
completed: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = true)::int`,
|
||||
})
|
||||
.from(companyReminders)
|
||||
.from(companyReminders);
|
||||
|
||||
if (accessibleCompanyIds !== null) {
|
||||
query = query.where(inArray(companyReminders.companyId, accessibleCompanyIds));
|
||||
}
|
||||
|
||||
const rows = await query
|
||||
.groupBy(companyReminders.companyId)
|
||||
.orderBy(desc(companyReminders.companyId));
|
||||
|
||||
@@ -140,12 +171,32 @@ export const getReminderCountsByCompany = async () => {
|
||||
* Get upcoming reminders for dashboard
|
||||
* Returns reminders due within the next 5 days that are not checked
|
||||
* Includes company name for display
|
||||
* For members: returns only reminders from companies they are assigned to
|
||||
*/
|
||||
export const getUpcomingReminders = async () => {
|
||||
export const getUpcomingReminders = async (userId = null, userRole = null) => {
|
||||
// Pre membera filtruj len pristupne firmy
|
||||
let accessibleCompanyIds = null;
|
||||
if (userRole && userRole !== 'admin' && userId) {
|
||||
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
||||
if (accessibleCompanyIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const fiveDaysFromNow = new Date();
|
||||
fiveDaysFromNow.setDate(fiveDaysFromNow.getDate() + 5);
|
||||
|
||||
const conditions = [
|
||||
eq(companyReminders.isChecked, false),
|
||||
lte(companyReminders.dueDate, fiveDaysFromNow),
|
||||
gte(companyReminders.dueDate, now)
|
||||
];
|
||||
|
||||
if (accessibleCompanyIds !== null) {
|
||||
conditions.push(inArray(companyReminders.companyId, accessibleCompanyIds));
|
||||
}
|
||||
|
||||
const reminders = await db
|
||||
.select({
|
||||
id: companyReminders.id,
|
||||
@@ -158,13 +209,7 @@ export const getUpcomingReminders = async () => {
|
||||
})
|
||||
.from(companyReminders)
|
||||
.leftJoin(companies, eq(companyReminders.companyId, companies.id))
|
||||
.where(
|
||||
and(
|
||||
eq(companyReminders.isChecked, false),
|
||||
lte(companyReminders.dueDate, fiveDaysFromNow),
|
||||
gte(companyReminders.dueDate, now)
|
||||
)
|
||||
)
|
||||
.where(and(...conditions))
|
||||
.orderBy(companyReminders.dueDate);
|
||||
|
||||
return reminders;
|
||||
|
||||
@@ -1,17 +1,58 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { companies, projects, todos, notes, companyReminders } from '../db/schema.js';
|
||||
import { eq, desc, ilike, or, and } from 'drizzle-orm';
|
||||
import { companies, projects, todos, notes, companyReminders, companyUsers, users } from '../db/schema.js';
|
||||
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
|
||||
/**
|
||||
* Get all companies
|
||||
* Optionally filter by search term
|
||||
* Returns companies with creator info
|
||||
* For members: returns only companies they are assigned to
|
||||
*/
|
||||
export const getAllCompanies = async (searchTerm = null) => {
|
||||
let query = db.select().from(companies);
|
||||
export const getAllCompanies = async (searchTerm = null, userId = null, userRole = null) => {
|
||||
// Pre membera najprv ziskaj pristupne company IDs
|
||||
let accessibleCompanyIds = null;
|
||||
if (userRole && userRole !== 'admin' && userId) {
|
||||
accessibleCompanyIds = await getAccessibleResourceIds('company', userId);
|
||||
// Ak member nema pristup k ziadnym firmam, vrat prazdne pole
|
||||
if (accessibleCompanyIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let query = db
|
||||
.select({
|
||||
id: companies.id,
|
||||
name: companies.name,
|
||||
description: companies.description,
|
||||
address: companies.address,
|
||||
city: companies.city,
|
||||
country: companies.country,
|
||||
phone: companies.phone,
|
||||
email: companies.email,
|
||||
website: companies.website,
|
||||
isActive: companies.isActive,
|
||||
createdBy: companies.createdBy,
|
||||
createdAt: companies.createdAt,
|
||||
updatedAt: companies.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(companies)
|
||||
.leftJoin(users, eq(companies.createdBy, users.id));
|
||||
|
||||
// Aplikuj filtrovanie pre membera
|
||||
const conditions = [];
|
||||
|
||||
if (accessibleCompanyIds !== null) {
|
||||
conditions.push(inArray(companies.id, accessibleCompanyIds));
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
query = query.where(
|
||||
conditions.push(
|
||||
or(
|
||||
ilike(companies.name, `%${searchTerm}%`),
|
||||
ilike(companies.email, `%${searchTerm}%`),
|
||||
@@ -20,17 +61,41 @@ export const getAllCompanies = async (searchTerm = null) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
query = query.where(and(...conditions));
|
||||
}
|
||||
|
||||
const result = await query.orderBy(desc(companies.createdAt));
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get company by ID
|
||||
* Returns company with creator info
|
||||
*/
|
||||
export const getCompanyById = async (companyId) => {
|
||||
const [company] = await db
|
||||
.select()
|
||||
.select({
|
||||
id: companies.id,
|
||||
name: companies.name,
|
||||
description: companies.description,
|
||||
address: companies.address,
|
||||
city: companies.city,
|
||||
country: companies.country,
|
||||
phone: companies.phone,
|
||||
email: companies.email,
|
||||
website: companies.website,
|
||||
isActive: companies.isActive,
|
||||
createdBy: companies.createdBy,
|
||||
createdAt: companies.createdAt,
|
||||
updatedAt: companies.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(companies)
|
||||
.leftJoin(users, eq(companies.createdBy, users.id))
|
||||
.where(eq(companies.id, companyId))
|
||||
.limit(1);
|
||||
|
||||
@@ -169,3 +234,167 @@ export const getCompanyWithRelations = async (companyId) => {
|
||||
reminders: companyReminderList,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all users assigned to a company
|
||||
*/
|
||||
export const getCompanyUsers = async (companyId) => {
|
||||
await getCompanyById(companyId); // Verify company exists
|
||||
|
||||
const rawResults = await db
|
||||
.select()
|
||||
.from(companyUsers)
|
||||
.leftJoin(users, eq(companyUsers.userId, users.id))
|
||||
.where(eq(companyUsers.companyId, companyId))
|
||||
.orderBy(desc(companyUsers.addedAt));
|
||||
|
||||
const assignedUsers = rawResults.map((row) => ({
|
||||
id: row.company_users.id,
|
||||
userId: row.company_users.userId,
|
||||
role: row.company_users.role,
|
||||
addedBy: row.company_users.addedBy,
|
||||
addedAt: row.company_users.addedAt,
|
||||
user: row.users ? {
|
||||
id: row.users.id,
|
||||
username: row.users.username,
|
||||
email: row.users.email,
|
||||
role: row.users.role,
|
||||
} : null,
|
||||
}));
|
||||
|
||||
return assignedUsers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Assign user to company
|
||||
*/
|
||||
export const assignUserToCompany = async (companyId, userId, addedByUserId, role = null) => {
|
||||
await getCompanyById(companyId); // Verify company exists
|
||||
|
||||
// Verify user exists
|
||||
const [user] = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundError('Používateľ nenájdený');
|
||||
}
|
||||
|
||||
// Check if user is already assigned
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(companyUsers)
|
||||
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)))
|
||||
.limit(1);
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictError('Používateľ je už priradený k firme');
|
||||
}
|
||||
|
||||
// Assign user to company
|
||||
const [assignment] = await db
|
||||
.insert(companyUsers)
|
||||
.values({
|
||||
companyId,
|
||||
userId,
|
||||
role: role || null,
|
||||
addedBy: addedByUserId,
|
||||
})
|
||||
.returning();
|
||||
|
||||
// Return with user details
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(companyUsers)
|
||||
.leftJoin(users, eq(companyUsers.userId, users.id))
|
||||
.where(eq(companyUsers.id, assignment.id))
|
||||
.limit(1);
|
||||
|
||||
return {
|
||||
id: row.company_users.id,
|
||||
userId: row.company_users.userId,
|
||||
role: row.company_users.role,
|
||||
addedBy: row.company_users.addedBy,
|
||||
addedAt: row.company_users.addedAt,
|
||||
user: row.users ? {
|
||||
id: row.users.id,
|
||||
username: row.users.username,
|
||||
email: row.users.email,
|
||||
role: row.users.role,
|
||||
} : null,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove user from company
|
||||
*/
|
||||
export const removeUserFromCompany = async (companyId, userId) => {
|
||||
await getCompanyById(companyId); // Verify company exists
|
||||
|
||||
// Check if user is assigned
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(companyUsers)
|
||||
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)))
|
||||
.limit(1);
|
||||
|
||||
if (!existing) {
|
||||
throw new NotFoundError('Používateľ nie je priradený k firme');
|
||||
}
|
||||
|
||||
// Remove assignment
|
||||
await db
|
||||
.delete(companyUsers)
|
||||
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)));
|
||||
|
||||
return { success: true, message: 'Používateľ bol odstránený z firmy' };
|
||||
};
|
||||
|
||||
/**
|
||||
* Update user role on company
|
||||
*/
|
||||
export const updateUserRoleOnCompany = async (companyId, userId, role) => {
|
||||
await getCompanyById(companyId); // Verify company exists
|
||||
|
||||
// Check if user is assigned
|
||||
const [existing] = await db
|
||||
.select()
|
||||
.from(companyUsers)
|
||||
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)))
|
||||
.limit(1);
|
||||
|
||||
if (!existing) {
|
||||
throw new NotFoundError('Používateľ nie je priradený k firme');
|
||||
}
|
||||
|
||||
// Update role
|
||||
const [updated] = await db
|
||||
.update(companyUsers)
|
||||
.set({ role: role || null })
|
||||
.where(and(eq(companyUsers.companyId, companyId), eq(companyUsers.userId, userId)))
|
||||
.returning();
|
||||
|
||||
// Return with user details
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(companyUsers)
|
||||
.leftJoin(users, eq(companyUsers.userId, users.id))
|
||||
.where(eq(companyUsers.id, updated.id))
|
||||
.limit(1);
|
||||
|
||||
return {
|
||||
id: row.company_users.id,
|
||||
userId: row.company_users.userId,
|
||||
role: row.company_users.role,
|
||||
addedBy: row.company_users.addedBy,
|
||||
addedAt: row.company_users.addedAt,
|
||||
user: row.users ? {
|
||||
id: row.users.id,
|
||||
username: row.users.username,
|
||||
email: row.users.email,
|
||||
role: row.users.role,
|
||||
} : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { notes, companies, projects, todos, contacts } from '../db/schema.js';
|
||||
import { notes, companies, projects, todos, contacts, users } from '../db/schema.js';
|
||||
import { eq, desc, ilike, or, and } from 'drizzle-orm';
|
||||
import { NotFoundError } from '../utils/errors.js';
|
||||
|
||||
/**
|
||||
* Get all notes
|
||||
* Optionally filter by search, company, project, todo, or contact
|
||||
* Returns notes with creator info
|
||||
*/
|
||||
export const getAllNotes = async (filters = {}) => {
|
||||
const { searchTerm, companyId, projectId, todoId, contactId } = filters;
|
||||
|
||||
let query = db.select().from(notes);
|
||||
let query = db
|
||||
.select({
|
||||
id: notes.id,
|
||||
title: notes.title,
|
||||
content: notes.content,
|
||||
companyId: notes.companyId,
|
||||
projectId: notes.projectId,
|
||||
todoId: notes.todoId,
|
||||
contactId: notes.contactId,
|
||||
createdBy: notes.createdBy,
|
||||
createdAt: notes.createdAt,
|
||||
updatedAt: notes.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(notes)
|
||||
.leftJoin(users, eq(notes.createdBy, users.id));
|
||||
|
||||
const conditions = [];
|
||||
|
||||
@@ -227,22 +246,56 @@ export const deleteNote = async (noteId) => {
|
||||
|
||||
/**
|
||||
* Get notes by company ID
|
||||
* Returns notes with creator info
|
||||
*/
|
||||
export const getNotesByCompanyId = async (companyId) => {
|
||||
return await db
|
||||
.select()
|
||||
.select({
|
||||
id: notes.id,
|
||||
title: notes.title,
|
||||
content: notes.content,
|
||||
companyId: notes.companyId,
|
||||
projectId: notes.projectId,
|
||||
todoId: notes.todoId,
|
||||
contactId: notes.contactId,
|
||||
createdBy: notes.createdBy,
|
||||
createdAt: notes.createdAt,
|
||||
updatedAt: notes.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(notes)
|
||||
.leftJoin(users, eq(notes.createdBy, users.id))
|
||||
.where(eq(notes.companyId, companyId))
|
||||
.orderBy(desc(notes.createdAt));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get notes by project ID
|
||||
* Returns notes with creator info
|
||||
*/
|
||||
export const getNotesByProjectId = async (projectId) => {
|
||||
return await db
|
||||
.select()
|
||||
.select({
|
||||
id: notes.id,
|
||||
title: notes.title,
|
||||
content: notes.content,
|
||||
companyId: notes.companyId,
|
||||
projectId: notes.projectId,
|
||||
todoId: notes.todoId,
|
||||
contactId: notes.contactId,
|
||||
createdBy: notes.createdBy,
|
||||
createdAt: notes.createdAt,
|
||||
updatedAt: notes.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(notes)
|
||||
.leftJoin(users, eq(notes.createdBy, users.id))
|
||||
.where(eq(notes.projectId, projectId))
|
||||
.orderBy(desc(notes.createdAt));
|
||||
};
|
||||
|
||||
@@ -1,17 +1,56 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { projects, todos, notes, timesheets, companies, projectUsers, users } from '../db/schema.js';
|
||||
import { eq, desc, ilike, or, and } from 'drizzle-orm';
|
||||
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||
import { NotFoundError, ConflictError } from '../utils/errors.js';
|
||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
|
||||
/**
|
||||
* Get all projects
|
||||
* Optionally filter by search term or company
|
||||
* Returns projects with creator info
|
||||
* For members: returns only projects they are directly assigned to
|
||||
*/
|
||||
export const getAllProjects = async (searchTerm = null, companyId = null) => {
|
||||
let query = db.select().from(projects);
|
||||
export const getAllProjects = async (searchTerm = null, companyId = null, userId = null, userRole = null) => {
|
||||
// Pre membera ziskaj pristupne project IDs
|
||||
let accessibleProjectIds = null;
|
||||
|
||||
if (userRole && userRole !== 'admin' && userId) {
|
||||
// Ziskaj projekty kde je user priamo priradeny
|
||||
accessibleProjectIds = await getAccessibleResourceIds('project', userId);
|
||||
|
||||
// Ak member nema pristup k ziadnym projektom, vrat prazdne pole
|
||||
if (accessibleProjectIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let query = db
|
||||
.select({
|
||||
id: projects.id,
|
||||
name: projects.name,
|
||||
description: projects.description,
|
||||
companyId: projects.companyId,
|
||||
status: projects.status,
|
||||
startDate: projects.startDate,
|
||||
endDate: projects.endDate,
|
||||
createdBy: projects.createdBy,
|
||||
createdAt: projects.createdAt,
|
||||
updatedAt: projects.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(projects)
|
||||
.leftJoin(users, eq(projects.createdBy, users.id));
|
||||
|
||||
const conditions = [];
|
||||
|
||||
// Filtrovanie pre membera - projekt je pristupny iba ak je user priamo priradeny
|
||||
if (accessibleProjectIds !== null) {
|
||||
conditions.push(inArray(projects.id, accessibleProjectIds));
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
conditions.push(
|
||||
or(
|
||||
@@ -35,11 +74,28 @@ export const getAllProjects = async (searchTerm = null, companyId = null) => {
|
||||
|
||||
/**
|
||||
* Get project by ID
|
||||
* Returns project with creator info
|
||||
*/
|
||||
export const getProjectById = async (projectId) => {
|
||||
const [project] = await db
|
||||
.select()
|
||||
.select({
|
||||
id: projects.id,
|
||||
name: projects.name,
|
||||
description: projects.description,
|
||||
companyId: projects.companyId,
|
||||
status: projects.status,
|
||||
startDate: projects.startDate,
|
||||
endDate: projects.endDate,
|
||||
createdBy: projects.createdBy,
|
||||
createdAt: projects.createdAt,
|
||||
updatedAt: projects.updatedAt,
|
||||
creator: {
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
},
|
||||
})
|
||||
.from(projects)
|
||||
.leftJoin(users, eq(projects.createdBy, users.id))
|
||||
.where(eq(projects.id, projectId))
|
||||
.limit(1);
|
||||
|
||||
|
||||
@@ -2,106 +2,50 @@ import { db } from '../config/database.js';
|
||||
import { todos, todoUsers, notes, projects, companies, users } from '../db/schema.js';
|
||||
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||
import { NotFoundError } from '../utils/errors.js';
|
||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
|
||||
/**
|
||||
* Get all todos
|
||||
* Optionally filter by search, project, company, assigned user, or status
|
||||
* For members: returns only todos they are assigned to
|
||||
*/
|
||||
export const getAllTodos = async (filters = {}) => {
|
||||
export const getAllTodos = async (filters = {}, userId = null, userRole = null) => {
|
||||
const { searchTerm, projectId, companyId, assignedTo, status, priority } = filters;
|
||||
|
||||
// If filtering by assignedTo, we need to join with todo_users
|
||||
// Pre membera filtruj len todos kde je priradeny
|
||||
let accessibleTodoIds = null;
|
||||
if (userRole && userRole !== 'admin' && userId) {
|
||||
accessibleTodoIds = await getAccessibleResourceIds('todo', userId);
|
||||
// Ak member nema pristup k ziadnym todos, vrat prazdne pole
|
||||
if (accessibleTodoIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Build query conditions
|
||||
const conditions = [];
|
||||
|
||||
// Member access filter - only todos they are assigned to
|
||||
if (accessibleTodoIds !== null) {
|
||||
conditions.push(inArray(todos.id, accessibleTodoIds));
|
||||
}
|
||||
|
||||
// If filtering by assignedTo, we need additional filter
|
||||
if (assignedTo) {
|
||||
const todoIdsWithUser = await db
|
||||
.select({ todoId: todoUsers.todoId })
|
||||
.from(todoUsers)
|
||||
.where(eq(todoUsers.userId, assignedTo));
|
||||
|
||||
const todoIds = todoIdsWithUser.map((row) => row.todoId);
|
||||
const assignedTodoIds = todoIdsWithUser.map((row) => row.todoId);
|
||||
|
||||
if (todoIds.length === 0) {
|
||||
if (assignedTodoIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let query = db.select().from(todos).where(inArray(todos.id, todoIds));
|
||||
|
||||
const conditions = [];
|
||||
|
||||
if (searchTerm) {
|
||||
conditions.push(
|
||||
or(
|
||||
ilike(todos.title, `%${searchTerm}%`),
|
||||
ilike(todos.description, `%${searchTerm}%`)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (projectId) {
|
||||
conditions.push(eq(todos.projectId, projectId));
|
||||
}
|
||||
|
||||
if (companyId) {
|
||||
conditions.push(eq(todos.companyId, companyId));
|
||||
}
|
||||
|
||||
if (status) {
|
||||
conditions.push(eq(todos.status, status));
|
||||
}
|
||||
|
||||
if (priority) {
|
||||
conditions.push(eq(todos.priority, priority));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
query = query.where(and(...conditions));
|
||||
}
|
||||
|
||||
const result = await query.orderBy(desc(todos.createdAt));
|
||||
|
||||
// Fetch assigned users for all todos
|
||||
if (result.length > 0) {
|
||||
const todoIds = result.map(todo => todo.id);
|
||||
const assignedUsersData = await db
|
||||
.select({
|
||||
todoId: todoUsers.todoId,
|
||||
userId: users.id,
|
||||
username: users.username,
|
||||
firstName: users.firstName,
|
||||
lastName: users.lastName,
|
||||
})
|
||||
.from(todoUsers)
|
||||
.innerJoin(users, eq(todoUsers.userId, users.id))
|
||||
.where(inArray(todoUsers.todoId, todoIds));
|
||||
|
||||
// Group assigned users by todoId
|
||||
const usersByTodoId = {};
|
||||
for (const row of assignedUsersData) {
|
||||
if (!usersByTodoId[row.todoId]) {
|
||||
usersByTodoId[row.todoId] = [];
|
||||
}
|
||||
usersByTodoId[row.todoId].push({
|
||||
id: row.userId,
|
||||
username: row.username,
|
||||
firstName: row.firstName,
|
||||
lastName: row.lastName,
|
||||
});
|
||||
}
|
||||
|
||||
// Attach assigned users to each todo
|
||||
return result.map(todo => ({
|
||||
...todo,
|
||||
assignedUsers: usersByTodoId[todo.id] || [],
|
||||
}));
|
||||
}
|
||||
|
||||
return result;
|
||||
conditions.push(inArray(todos.id, assignedTodoIds));
|
||||
}
|
||||
|
||||
// No assignedTo filter - simple query
|
||||
let query = db.select().from(todos);
|
||||
|
||||
const conditions = [];
|
||||
|
||||
if (searchTerm) {
|
||||
conditions.push(
|
||||
or(
|
||||
@@ -127,6 +71,8 @@ export const getAllTodos = async (filters = {}) => {
|
||||
conditions.push(eq(todos.priority, priority));
|
||||
}
|
||||
|
||||
let query = db.select().from(todos);
|
||||
|
||||
if (conditions.length > 0) {
|
||||
query = query.where(and(...conditions));
|
||||
}
|
||||
@@ -135,18 +81,18 @@ export const getAllTodos = async (filters = {}) => {
|
||||
|
||||
// Fetch assigned users for all todos
|
||||
if (result.length > 0) {
|
||||
const todoIds = result.map(todo => todo.id);
|
||||
const resultTodoIds = result.map(todo => todo.id);
|
||||
const assignedUsersData = await db
|
||||
.select({
|
||||
todoId: todoUsers.todoId,
|
||||
userId: users.id,
|
||||
odUserId: users.id,
|
||||
username: users.username,
|
||||
firstName: users.firstName,
|
||||
lastName: users.lastName,
|
||||
})
|
||||
.from(todoUsers)
|
||||
.innerJoin(users, eq(todoUsers.userId, users.id))
|
||||
.where(inArray(todoUsers.todoId, todoIds));
|
||||
.where(inArray(todoUsers.todoId, resultTodoIds));
|
||||
|
||||
// Group assigned users by todoId
|
||||
const usersByTodoId = {};
|
||||
@@ -155,7 +101,7 @@ export const getAllTodos = async (filters = {}) => {
|
||||
usersByTodoId[row.todoId] = [];
|
||||
}
|
||||
usersByTodoId[row.todoId].push({
|
||||
id: row.userId,
|
||||
id: row.odUserId,
|
||||
username: row.username,
|
||||
firstName: row.firstName,
|
||||
lastName: row.lastName,
|
||||
|
||||
Reference in New Issue
Block a user