@faryzal2020/v-perms 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +527 -0
- package/package.json +41 -0
- package/src/adapters/BaseAdapter.js +262 -0
- package/src/adapters/PrismaAdapter.js +476 -0
- package/src/adapters/index.js +5 -0
- package/src/core/CacheManager.js +151 -0
- package/src/core/PermissionChecker.js +203 -0
- package/src/core/PermissionManager.js +410 -0
- package/src/core/errors.js +92 -0
- package/src/index.js +73 -0
- package/src/prisma/schema.prisma +86 -0
- package/src/utils/logger.js +64 -0
- package/src/utils/wildcard.js +44 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import BaseAdapter from './BaseAdapter.js';
|
|
2
|
+
import {
|
|
3
|
+
RoleNotFoundError,
|
|
4
|
+
PermissionNotFoundError,
|
|
5
|
+
RoleAlreadyAssignedError,
|
|
6
|
+
PermissionAlreadyExistsError,
|
|
7
|
+
RoleAlreadyExistsError,
|
|
8
|
+
CircularInheritanceError,
|
|
9
|
+
} from '../core/errors.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Prisma database adapter implementation
|
|
13
|
+
*/
|
|
14
|
+
class PrismaAdapter extends BaseAdapter {
|
|
15
|
+
constructor(prismaClient, logger) {
|
|
16
|
+
super();
|
|
17
|
+
this.prisma = prismaClient;
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ==================== User Operations ====================
|
|
22
|
+
|
|
23
|
+
async getUserRoles(userId) {
|
|
24
|
+
this.logger.debug('getUserRoles:', userId);
|
|
25
|
+
|
|
26
|
+
const userRoles = await this.prisma.userRole.findMany({
|
|
27
|
+
where: { userId },
|
|
28
|
+
include: { role: true },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return userRoles.map(ur => ur.role);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async assignRoleToUser(userId, roleId) {
|
|
35
|
+
this.logger.debug('assignRoleToUser:', userId, roleId);
|
|
36
|
+
|
|
37
|
+
// Check if role exists
|
|
38
|
+
const role = await this.getRole(roleId);
|
|
39
|
+
if (!role) {
|
|
40
|
+
throw new RoleNotFoundError(roleId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if already assigned
|
|
44
|
+
const existing = await this.prisma.userRole.findUnique({
|
|
45
|
+
where: {
|
|
46
|
+
userId_roleId: { userId, roleId },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (existing) {
|
|
51
|
+
throw new RoleAlreadyAssignedError(userId, roleId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return await this.prisma.userRole.create({
|
|
55
|
+
data: { userId, roleId },
|
|
56
|
+
include: { role: true },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async removeRoleFromUser(userId, roleId) {
|
|
61
|
+
this.logger.debug('removeRoleFromUser:', userId, roleId);
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await this.prisma.userRole.delete({
|
|
65
|
+
where: {
|
|
66
|
+
userId_roleId: { userId, roleId },
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error.code === 'P2025') {
|
|
72
|
+
// Record not found
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async userHasRole(userId, roleId) {
|
|
80
|
+
this.logger.debug('userHasRole:', userId, roleId);
|
|
81
|
+
|
|
82
|
+
const userRole = await this.prisma.userRole.findUnique({
|
|
83
|
+
where: {
|
|
84
|
+
userId_roleId: { userId, roleId },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return !!userRole;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ==================== Role Operations ====================
|
|
92
|
+
|
|
93
|
+
async createRole(data) {
|
|
94
|
+
this.logger.debug('createRole:', data);
|
|
95
|
+
|
|
96
|
+
// Check if role already exists
|
|
97
|
+
const existing = await this.getRoleByName(data.name);
|
|
98
|
+
if (existing) {
|
|
99
|
+
throw new RoleAlreadyExistsError(data.name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return await this.prisma.role.create({
|
|
103
|
+
data: {
|
|
104
|
+
name: data.name,
|
|
105
|
+
description: data.description || null,
|
|
106
|
+
priority: data.priority || 0,
|
|
107
|
+
isDefault: data.isDefault || false,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getRole(roleId) {
|
|
113
|
+
this.logger.debug('getRole:', roleId);
|
|
114
|
+
|
|
115
|
+
return await this.prisma.role.findUnique({
|
|
116
|
+
where: { id: roleId },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getRoleByName(name) {
|
|
121
|
+
this.logger.debug('getRoleByName:', name);
|
|
122
|
+
|
|
123
|
+
return await this.prisma.role.findUnique({
|
|
124
|
+
where: { name },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async updateRole(roleId, data) {
|
|
129
|
+
this.logger.debug('updateRole:', roleId, data);
|
|
130
|
+
|
|
131
|
+
return await this.prisma.role.update({
|
|
132
|
+
where: { id: roleId },
|
|
133
|
+
data,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async deleteRole(roleId) {
|
|
138
|
+
this.logger.debug('deleteRole:', roleId);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
await this.prisma.role.delete({
|
|
142
|
+
where: { id: roleId },
|
|
143
|
+
});
|
|
144
|
+
return true;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (error.code === 'P2025') {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getRolePermissions(roleId) {
|
|
154
|
+
this.logger.debug('getRolePermissions:', roleId);
|
|
155
|
+
|
|
156
|
+
const rolePermissions = await this.prisma.rolePermission.findMany({
|
|
157
|
+
where: { roleId },
|
|
158
|
+
include: { permission: true },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return rolePermissions.map(rp => ({
|
|
162
|
+
...rp.permission,
|
|
163
|
+
granted: rp.granted,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getRoleInheritance(roleId) {
|
|
168
|
+
this.logger.debug('getRoleInheritance:', roleId);
|
|
169
|
+
|
|
170
|
+
return await this.prisma.roleInheritance.findMany({
|
|
171
|
+
where: { roleId },
|
|
172
|
+
include: { inheritsFrom: true },
|
|
173
|
+
orderBy: { priority: 'desc' },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async setRoleInheritance(roleId, inheritsFromId, priority = 0) {
|
|
178
|
+
this.logger.debug('setRoleInheritance:', roleId, inheritsFromId, priority);
|
|
179
|
+
|
|
180
|
+
// Prevent self-inheritance
|
|
181
|
+
if (roleId === inheritsFromId) {
|
|
182
|
+
throw new CircularInheritanceError(roleId, inheritsFromId);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check for circular inheritance
|
|
186
|
+
const hasCircular = await this._checkCircularInheritance(roleId, inheritsFromId);
|
|
187
|
+
if (hasCircular) {
|
|
188
|
+
throw new CircularInheritanceError(roleId, inheritsFromId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Upsert the inheritance
|
|
192
|
+
return await this.prisma.roleInheritance.upsert({
|
|
193
|
+
where: {
|
|
194
|
+
roleId_inheritsFromId: { roleId, inheritsFromId },
|
|
195
|
+
},
|
|
196
|
+
create: { roleId, inheritsFromId, priority },
|
|
197
|
+
update: { priority },
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async removeRoleInheritance(roleId, inheritsFromId) {
|
|
202
|
+
this.logger.debug('removeRoleInheritance:', roleId, inheritsFromId);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
await this.prisma.roleInheritance.delete({
|
|
206
|
+
where: {
|
|
207
|
+
roleId_inheritsFromId: { roleId, inheritsFromId },
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
return true;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error.code === 'P2025') {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check for circular inheritance
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
async _checkCircularInheritance(roleId, inheritsFromId, visited = new Set()) {
|
|
224
|
+
if (visited.has(inheritsFromId)) {
|
|
225
|
+
return false; // Already checked this path
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
visited.add(inheritsFromId);
|
|
229
|
+
|
|
230
|
+
// Get what the inheritsFromId role inherits
|
|
231
|
+
const inheritances = await this.prisma.roleInheritance.findMany({
|
|
232
|
+
where: { roleId: inheritsFromId },
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
for (const inheritance of inheritances) {
|
|
236
|
+
// If the role we're trying to add inherits back to the original role, circular!
|
|
237
|
+
if (inheritance.inheritsFromId === roleId) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Recursively check
|
|
242
|
+
const hasCircular = await this._checkCircularInheritance(roleId, inheritance.inheritsFromId, visited);
|
|
243
|
+
if (hasCircular) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ==================== Permission Operations ====================
|
|
252
|
+
|
|
253
|
+
async createPermission(data) {
|
|
254
|
+
this.logger.debug('createPermission:', data);
|
|
255
|
+
|
|
256
|
+
// Check if permission already exists
|
|
257
|
+
const existing = await this.getPermission(data.key);
|
|
258
|
+
if (existing) {
|
|
259
|
+
throw new PermissionAlreadyExistsError(data.key);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return await this.prisma.permission.create({
|
|
263
|
+
data: {
|
|
264
|
+
key: data.key,
|
|
265
|
+
description: data.description || null,
|
|
266
|
+
category: data.category || null,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async getPermission(permissionKey) {
|
|
272
|
+
this.logger.debug('getPermission:', permissionKey);
|
|
273
|
+
|
|
274
|
+
return await this.prisma.permission.findUnique({
|
|
275
|
+
where: { key: permissionKey },
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async getPermissionById(permissionId) {
|
|
280
|
+
this.logger.debug('getPermissionById:', permissionId);
|
|
281
|
+
|
|
282
|
+
return await this.prisma.permission.findUnique({
|
|
283
|
+
where: { id: permissionId },
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async deletePermission(permissionKey) {
|
|
288
|
+
this.logger.debug('deletePermission:', permissionKey);
|
|
289
|
+
|
|
290
|
+
const permission = await this.getPermission(permissionKey);
|
|
291
|
+
if (!permission) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
await this.prisma.permission.delete({
|
|
297
|
+
where: { key: permissionKey },
|
|
298
|
+
});
|
|
299
|
+
return true;
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (error.code === 'P2025') {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async assignPermissionToRole(permissionKey, roleId, granted = true) {
|
|
309
|
+
this.logger.debug('assignPermissionToRole:', permissionKey, roleId, granted);
|
|
310
|
+
|
|
311
|
+
// Get or create permission
|
|
312
|
+
let permission = await this.getPermission(permissionKey);
|
|
313
|
+
if (!permission) {
|
|
314
|
+
// Create wildcard or regular permission
|
|
315
|
+
permission = await this.createPermission({ key: permissionKey });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Check if role exists
|
|
319
|
+
const role = await this.getRole(roleId);
|
|
320
|
+
if (!role) {
|
|
321
|
+
throw new RoleNotFoundError(roleId);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Upsert the role permission
|
|
325
|
+
return await this.prisma.rolePermission.upsert({
|
|
326
|
+
where: {
|
|
327
|
+
roleId_permissionId: { roleId, permissionId: permission.id },
|
|
328
|
+
},
|
|
329
|
+
create: { roleId, permissionId: permission.id, granted },
|
|
330
|
+
update: { granted },
|
|
331
|
+
include: { permission: true },
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async removePermissionFromRole(permissionKey, roleId) {
|
|
336
|
+
this.logger.debug('removePermissionFromRole:', permissionKey, roleId);
|
|
337
|
+
|
|
338
|
+
const permission = await this.getPermission(permissionKey);
|
|
339
|
+
if (!permission) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
await this.prisma.rolePermission.delete({
|
|
345
|
+
where: {
|
|
346
|
+
roleId_permissionId: { roleId, permissionId: permission.id },
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
return true;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
if (error.code === 'P2025') {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async assignPermissionToUser(permissionKey, userId, granted = true) {
|
|
359
|
+
this.logger.debug('assignPermissionToUser:', permissionKey, userId, granted);
|
|
360
|
+
|
|
361
|
+
// Get or create permission
|
|
362
|
+
let permission = await this.getPermission(permissionKey);
|
|
363
|
+
if (!permission) {
|
|
364
|
+
permission = await this.createPermission({ key: permissionKey });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Upsert the user permission
|
|
368
|
+
return await this.prisma.userPermission.upsert({
|
|
369
|
+
where: {
|
|
370
|
+
userId_permissionId: { userId, permissionId: permission.id },
|
|
371
|
+
},
|
|
372
|
+
create: { userId, permissionId: permission.id, granted },
|
|
373
|
+
update: { granted },
|
|
374
|
+
include: { permission: true },
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async removePermissionFromUser(permissionKey, userId) {
|
|
379
|
+
this.logger.debug('removePermissionFromUser:', permissionKey, userId);
|
|
380
|
+
|
|
381
|
+
const permission = await this.getPermission(permissionKey);
|
|
382
|
+
if (!permission) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
await this.prisma.userPermission.delete({
|
|
388
|
+
where: {
|
|
389
|
+
userId_permissionId: { userId, permissionId: permission.id },
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
return true;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
if (error.code === 'P2025') {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async getUserDirectPermissions(userId) {
|
|
402
|
+
this.logger.debug('getUserDirectPermissions:', userId);
|
|
403
|
+
|
|
404
|
+
const userPermissions = await this.prisma.userPermission.findMany({
|
|
405
|
+
where: { userId },
|
|
406
|
+
include: { permission: true },
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
return userPermissions.map(up => ({
|
|
410
|
+
...up.permission,
|
|
411
|
+
granted: up.granted,
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async getRolePermission(roleId, permissionKey) {
|
|
416
|
+
this.logger.debug('getRolePermission:', roleId, permissionKey);
|
|
417
|
+
|
|
418
|
+
const permission = await this.getPermission(permissionKey);
|
|
419
|
+
if (!permission) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const rolePermission = await this.prisma.rolePermission.findUnique({
|
|
424
|
+
where: {
|
|
425
|
+
roleId_permissionId: { roleId, permissionId: permission.id },
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
if (!rolePermission) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return { granted: rolePermission.granted };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async getUserPermission(userId, permissionKey) {
|
|
437
|
+
this.logger.debug('getUserPermission:', userId, permissionKey);
|
|
438
|
+
|
|
439
|
+
const permission = await this.getPermission(permissionKey);
|
|
440
|
+
if (!permission) {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const userPermission = await this.prisma.userPermission.findUnique({
|
|
445
|
+
where: {
|
|
446
|
+
userId_permissionId: { userId, permissionId: permission.id },
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
if (!userPermission) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return { granted: userPermission.granted };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ==================== Listing Operations ====================
|
|
458
|
+
|
|
459
|
+
async listAllPermissions() {
|
|
460
|
+
this.logger.debug('listAllPermissions');
|
|
461
|
+
|
|
462
|
+
return await this.prisma.permission.findMany({
|
|
463
|
+
orderBy: { key: 'asc' },
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async listAllRoles() {
|
|
468
|
+
this.logger.debug('listAllRoles');
|
|
469
|
+
|
|
470
|
+
return await this.prisma.role.findMany({
|
|
471
|
+
orderBy: { priority: 'desc' },
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export default PrismaAdapter;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache manager for Redis-based caching of permission checks
|
|
3
|
+
*/
|
|
4
|
+
class CacheManager {
|
|
5
|
+
constructor(redisClient, options = {}) {
|
|
6
|
+
this.redis = redisClient;
|
|
7
|
+
this.options = {
|
|
8
|
+
enabled: true,
|
|
9
|
+
ttl: 300, // 5 minutes in seconds
|
|
10
|
+
prefix: 'v-perms:',
|
|
11
|
+
...options,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build a cache key from type and parts
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
_buildKey(type, ...parts) {
|
|
20
|
+
return `${this.options.prefix}${type}:${parts.join(':')}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get value from cache
|
|
25
|
+
* @param {string} type - Cache type (e.g., 'user', 'role')
|
|
26
|
+
* @param {...string} parts - Key parts
|
|
27
|
+
* @returns {Promise<any|null>}
|
|
28
|
+
*/
|
|
29
|
+
async get(type, ...parts) {
|
|
30
|
+
if (!this.options.enabled || !this.redis) return null;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const key = this._buildKey(type, ...parts);
|
|
34
|
+
const value = await this.redis.get(key);
|
|
35
|
+
return value ? JSON.parse(value) : null;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Fail silently on cache errors
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set value in cache
|
|
44
|
+
* @param {string} type - Cache type
|
|
45
|
+
* @param {any} value - Value to cache
|
|
46
|
+
* @param {...string} parts - Key parts
|
|
47
|
+
*/
|
|
48
|
+
async set(type, value, ...parts) {
|
|
49
|
+
if (!this.options.enabled || !this.redis) return;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const key = this._buildKey(type, ...parts);
|
|
53
|
+
await this.redis.setEx(key, this.options.ttl, JSON.stringify(value));
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Fail silently on cache errors
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Delete specific cache entry
|
|
61
|
+
* @param {string} type - Cache type
|
|
62
|
+
* @param {...string} parts - Key parts
|
|
63
|
+
*/
|
|
64
|
+
async delete(type, ...parts) {
|
|
65
|
+
if (!this.redis) return;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const key = this._buildKey(type, ...parts);
|
|
69
|
+
await this.redis.del(key);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// Fail silently
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Invalidate all cache entries for a user
|
|
77
|
+
* @param {string} userId
|
|
78
|
+
*/
|
|
79
|
+
async invalidateUser(userId) {
|
|
80
|
+
if (!this.redis) return;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const pattern = this._buildKey('user', userId, '*');
|
|
84
|
+
const keys = await this.redis.keys(pattern);
|
|
85
|
+
if (keys.length > 0) {
|
|
86
|
+
await this.redis.del(...keys);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// Fail silently
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Invalidate all cache entries for a role
|
|
95
|
+
* @param {string} roleId
|
|
96
|
+
*/
|
|
97
|
+
async invalidateRole(roleId) {
|
|
98
|
+
if (!this.redis) return;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const pattern = this._buildKey('role', roleId, '*');
|
|
102
|
+
const keys = await this.redis.keys(pattern);
|
|
103
|
+
if (keys.length > 0) {
|
|
104
|
+
await this.redis.del(...keys);
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Fail silently
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clear entire permission cache
|
|
113
|
+
*/
|
|
114
|
+
async clear() {
|
|
115
|
+
if (!this.redis) return;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const pattern = `${this.options.prefix}*`;
|
|
119
|
+
const keys = await this.redis.keys(pattern);
|
|
120
|
+
if (keys.length > 0) {
|
|
121
|
+
await this.redis.del(...keys);
|
|
122
|
+
}
|
|
123
|
+
} catch (error) {
|
|
124
|
+
// Fail silently
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if caching is enabled and available
|
|
130
|
+
* @returns {boolean}
|
|
131
|
+
*/
|
|
132
|
+
isEnabled() {
|
|
133
|
+
return this.options.enabled && this.redis !== null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Enable caching
|
|
138
|
+
*/
|
|
139
|
+
enable() {
|
|
140
|
+
this.options.enabled = true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Disable caching
|
|
145
|
+
*/
|
|
146
|
+
disable() {
|
|
147
|
+
this.options.enabled = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default CacheManager;
|