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

@@ -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);
};