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:
richardtekula
2025-12-12 07:41:57 +01:00
parent 918af3a843
commit 8656fb1db0
14 changed files with 2175 additions and 125 deletions

View File

@@ -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,