perf(access): TTL-кэш getAccessibleTaskIds с точечной инвалидацией
Шаг 0.12 плана production-готовности: - accessibleTasksCache (45с) per userId:orgId внутри storage-метода - инвалидация в storage-слое: роли, делегирования, form access, assignees, createTask/updateTask, appRole пользователя - 9 новых юнит-тестов (65/65 зелёные)
This commit is contained in:
@@ -2,6 +2,7 @@ import { users, appRoles, permissions, appRolePermissions, roleMembers, roles }
|
||||
import { db } from "../db";
|
||||
import { eq, and, asc, inArray } from "drizzle-orm";
|
||||
import { BillingStorage } from "./billing.storage";
|
||||
import { invalidateAccessibleTasksForUser, invalidateAccessibleTasksForOrg } from "../utils/cache";
|
||||
|
||||
// In-memory permission cache
|
||||
let _permCache: Map<string, string[]> | null = null;
|
||||
@@ -163,6 +164,10 @@ export class RolesStorage extends BillingStorage {
|
||||
if (!manager) throw new Error('Руководитель не найден в этой организации');
|
||||
}
|
||||
const [role] = await db.update(roles).set(updates).where(and(eq(roles.id, id), eq(roles.organizationId, organizationId))).returning();
|
||||
// Смена parentId меняет наследование доступа по иерархии ролей — сброс по организации
|
||||
if ('parentId' in updates) {
|
||||
invalidateAccessibleTasksForOrg(organizationId);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
@@ -170,6 +175,7 @@ export class RolesStorage extends BillingStorage {
|
||||
const { roles } = await import('@shared/schema');
|
||||
await db.update(roles).set({ parentId: null }).where(and(eq(roles.parentId, id), eq(roles.organizationId, organizationId)));
|
||||
await db.delete(roles).where(and(eq(roles.id, id), eq(roles.organizationId, organizationId)));
|
||||
invalidateAccessibleTasksForOrg(organizationId);
|
||||
}
|
||||
|
||||
async getRoleMembersByRole(roleId: number): Promise<{ id: number; userId: number; createdAt: Date | null }[]> {
|
||||
@@ -217,6 +223,7 @@ export class RolesStorage extends BillingStorage {
|
||||
const [targetUser] = await db.select().from(users).where(and(eq(users.id, userId), eq(users.organizationId, organizationId)));
|
||||
if (!targetUser) throw new Error('Пользователь не найден в этой организации');
|
||||
await db.insert(roleMembers).values({ roleId, userId }).onConflictDoNothing();
|
||||
invalidateAccessibleTasksForUser(userId, organizationId);
|
||||
}
|
||||
|
||||
async removeRoleMember(roleId: number, userId: number, organizationId: number) {
|
||||
@@ -226,6 +233,7 @@ export class RolesStorage extends BillingStorage {
|
||||
const [targetUser] = await db.select().from(users).where(and(eq(users.id, userId), eq(users.organizationId, organizationId)));
|
||||
if (!targetUser) throw new Error('Пользователь не найден в этой организации');
|
||||
await db.delete(roleMembers).where(and(eq(roleMembers.roleId, roleId), eq(roleMembers.userId, userId)));
|
||||
invalidateAccessibleTasksForUser(userId, organizationId);
|
||||
}
|
||||
|
||||
async moveRoleMember(sourceRoleId: number, targetRoleId: number | null, userId: number, organizationId: number) {
|
||||
@@ -247,6 +255,7 @@ export class RolesStorage extends BillingStorage {
|
||||
if (targetRoleId !== null) {
|
||||
await db.insert(roleMembers).values({ roleId: targetRoleId, userId }).onConflictDoNothing();
|
||||
}
|
||||
invalidateAccessibleTasksForUser(userId, organizationId);
|
||||
}
|
||||
|
||||
async getUserRoles(userId: number, organizationId: number) {
|
||||
@@ -343,6 +352,11 @@ export class RolesStorage extends BillingStorage {
|
||||
const values = roleIds.map(roleId => ({ userId, roleId }));
|
||||
await db.insert(roleMembers).values(values).onConflictDoNothing();
|
||||
}
|
||||
// organizationId не приходит аргументом — сбрасываем кэш по всем организациям пользователя
|
||||
const userRows = await db.select({ organizationId: users.organizationId }).from(users).where(eq(users.id, userId)).limit(1);
|
||||
if (userRows[0]?.organizationId) {
|
||||
invalidateAccessibleTasksForUser(userId, userRows[0].organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
async getUserOrganizationalRoles(userId: number, organizationId: number) {
|
||||
|
||||
Reference in New Issue
Block a user