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

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