@hiliosai/sdk 0.1.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/package.json +27 -0
- package/src/configs/index.ts +1 -0
- package/src/configs/permissions.ts +109 -0
- package/src/env.ts +12 -0
- package/src/errors/auth.error.ts +33 -0
- package/src/errors/index.ts +2 -0
- package/src/errors/permission.error.ts +17 -0
- package/src/examples/cache-usage.example.ts +293 -0
- package/src/index.ts +5 -0
- package/src/middlewares/cache.middleware.ts +278 -0
- package/src/middlewares/context-helpers.middleware.ts +159 -0
- package/src/middlewares/datasource.middleware.ts +71 -0
- package/src/middlewares/health.middleware.ts +134 -0
- package/src/middlewares/index.ts +6 -0
- package/src/middlewares/memoize.middleware.ts +33 -0
- package/src/middlewares/permissions.middleware.ts +162 -0
- package/src/service/define-integration.ts +237 -0
- package/src/service/define-service.ts +77 -0
- package/src/service/example-user/datasources/index.ts +8 -0
- package/src/service/example-user/datasources/user.datasource.ts +7 -0
- package/src/service/example-user/user.service.ts +31 -0
- package/src/service/example-user/utils.ts +0 -0
- package/src/types/context.ts +41 -0
- package/src/types/datasource.ts +23 -0
- package/src/types/index.ts +8 -0
- package/src/types/integration.ts +48 -0
- package/src/types/message.ts +95 -0
- package/src/types/platform.ts +39 -0
- package/src/types/service.ts +208 -0
- package/src/types/tenant.ts +4 -0
- package/src/types/user.ts +16 -0
- package/src/utils/context-cache.ts +70 -0
- package/src/utils/permission-calculator.ts +62 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +6 -0
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hiliosai/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"format": "bunx prettier --write ./**/*.{ts,json,md,yaml}"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@keyv/redis": "5.1.5",
|
|
14
|
+
"@ltv/env": "4.0.3",
|
|
15
|
+
"keyv": "5.5.5",
|
|
16
|
+
"lodash": "4.17.21",
|
|
17
|
+
"moleculer": "0.14.35"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@hiliosai/prettier": "workspace:*",
|
|
21
|
+
"@hiliosai/typescript": "workspace:*",
|
|
22
|
+
"@pkg/dev-utils": "workspace:*",
|
|
23
|
+
"@types/lodash": "4.17.21",
|
|
24
|
+
"bun-types": "latest"
|
|
25
|
+
},
|
|
26
|
+
"prettier": "@hiliosai/prettier"
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './permissions';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Permission constants
|
|
2
|
+
export const PERMISSIONS = {
|
|
3
|
+
// Authentication required
|
|
4
|
+
AUTHENTICATED: 'authenticated',
|
|
5
|
+
|
|
6
|
+
// Role-based permissions
|
|
7
|
+
OWNER: 'OWNER',
|
|
8
|
+
ADMIN: 'ADMIN',
|
|
9
|
+
MANAGER: 'MANAGER',
|
|
10
|
+
AGENT: 'AGENT',
|
|
11
|
+
VIEWER: 'VIEWER',
|
|
12
|
+
DEVELOPER: 'DEVELOPER',
|
|
13
|
+
|
|
14
|
+
// Entity-specific permissions
|
|
15
|
+
TENANT_OWNER: 'tenant.owner',
|
|
16
|
+
TENANT_MEMBER: 'tenant.member',
|
|
17
|
+
|
|
18
|
+
// Resource permissions
|
|
19
|
+
'users.read': 'users.read',
|
|
20
|
+
'users.write': 'users.write',
|
|
21
|
+
'users.delete': 'users.delete',
|
|
22
|
+
'tenants.read': 'tenants.read',
|
|
23
|
+
'tenants.write': 'tenants.write',
|
|
24
|
+
'tenants.delete': 'tenants.delete',
|
|
25
|
+
'conversations.read': 'conversations.read',
|
|
26
|
+
'conversations.write': 'conversations.write',
|
|
27
|
+
'conversations.delete': 'conversations.delete',
|
|
28
|
+
'messages.read': 'messages.read',
|
|
29
|
+
'messages.write': 'messages.write',
|
|
30
|
+
'settings.read': 'settings.read',
|
|
31
|
+
'settings.write': 'settings.write',
|
|
32
|
+
'config.read': 'config.read',
|
|
33
|
+
'config.write': 'config.write',
|
|
34
|
+
'billing.read': 'billing.read',
|
|
35
|
+
'billing.write': 'billing.write',
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
// Role hierarchy and permissions mapping
|
|
39
|
+
export const ROLE_PERMISSIONS: Record<string, string[]> = {
|
|
40
|
+
[PERMISSIONS.OWNER]: [
|
|
41
|
+
PERMISSIONS.AUTHENTICATED,
|
|
42
|
+
PERMISSIONS['users.read'],
|
|
43
|
+
PERMISSIONS['users.write'],
|
|
44
|
+
PERMISSIONS['users.delete'],
|
|
45
|
+
PERMISSIONS['tenants.read'],
|
|
46
|
+
PERMISSIONS['tenants.write'],
|
|
47
|
+
PERMISSIONS['tenants.delete'],
|
|
48
|
+
PERMISSIONS['conversations.read'],
|
|
49
|
+
PERMISSIONS['conversations.write'],
|
|
50
|
+
PERMISSIONS['conversations.delete'],
|
|
51
|
+
PERMISSIONS['messages.read'],
|
|
52
|
+
PERMISSIONS['messages.write'],
|
|
53
|
+
PERMISSIONS['settings.read'],
|
|
54
|
+
PERMISSIONS['settings.write'],
|
|
55
|
+
PERMISSIONS['config.read'],
|
|
56
|
+
PERMISSIONS['config.write'],
|
|
57
|
+
PERMISSIONS['billing.read'],
|
|
58
|
+
PERMISSIONS['billing.write'],
|
|
59
|
+
],
|
|
60
|
+
[PERMISSIONS.ADMIN]: [
|
|
61
|
+
PERMISSIONS.AUTHENTICATED,
|
|
62
|
+
PERMISSIONS['users.read'],
|
|
63
|
+
PERMISSIONS['users.write'],
|
|
64
|
+
PERMISSIONS['users.delete'],
|
|
65
|
+
PERMISSIONS['tenants.read'],
|
|
66
|
+
PERMISSIONS['tenants.write'],
|
|
67
|
+
PERMISSIONS['tenants.delete'],
|
|
68
|
+
PERMISSIONS['conversations.read'],
|
|
69
|
+
PERMISSIONS['conversations.write'],
|
|
70
|
+
PERMISSIONS['conversations.delete'],
|
|
71
|
+
PERMISSIONS['messages.read'],
|
|
72
|
+
PERMISSIONS['messages.write'],
|
|
73
|
+
PERMISSIONS['settings.read'],
|
|
74
|
+
PERMISSIONS['settings.write'],
|
|
75
|
+
PERMISSIONS['config.read'],
|
|
76
|
+
PERMISSIONS['config.write'],
|
|
77
|
+
PERMISSIONS['billing.read'],
|
|
78
|
+
],
|
|
79
|
+
[PERMISSIONS.MANAGER]: [
|
|
80
|
+
PERMISSIONS.AUTHENTICATED,
|
|
81
|
+
PERMISSIONS['users.read'],
|
|
82
|
+
PERMISSIONS['users.write'],
|
|
83
|
+
PERMISSIONS['conversations.read'],
|
|
84
|
+
PERMISSIONS['conversations.write'],
|
|
85
|
+
PERMISSIONS['messages.read'],
|
|
86
|
+
PERMISSIONS['messages.write'],
|
|
87
|
+
PERMISSIONS['settings.read'],
|
|
88
|
+
PERMISSIONS['settings.write'],
|
|
89
|
+
],
|
|
90
|
+
[PERMISSIONS.AGENT]: [
|
|
91
|
+
PERMISSIONS.AUTHENTICATED,
|
|
92
|
+
PERMISSIONS['conversations.read'],
|
|
93
|
+
PERMISSIONS['conversations.write'],
|
|
94
|
+
PERMISSIONS['messages.read'],
|
|
95
|
+
PERMISSIONS['messages.write'],
|
|
96
|
+
],
|
|
97
|
+
[PERMISSIONS.VIEWER]: [
|
|
98
|
+
PERMISSIONS.AUTHENTICATED,
|
|
99
|
+
PERMISSIONS['conversations.read'],
|
|
100
|
+
PERMISSIONS['messages.read'],
|
|
101
|
+
],
|
|
102
|
+
[PERMISSIONS.DEVELOPER]: [
|
|
103
|
+
PERMISSIONS.AUTHENTICATED,
|
|
104
|
+
// Only specific debug/development permissions, not full access
|
|
105
|
+
'system.debug',
|
|
106
|
+
'health.check',
|
|
107
|
+
PERMISSIONS['config.read'],
|
|
108
|
+
],
|
|
109
|
+
};
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import env from '@ltv/env';
|
|
2
|
+
|
|
3
|
+
const nodeEnv = env.string('NODE_ENV', 'development');
|
|
4
|
+
|
|
5
|
+
export const isDev = nodeEnv === 'development';
|
|
6
|
+
export const isTest = nodeEnv === 'test';
|
|
7
|
+
export const isProd = nodeEnv === 'production';
|
|
8
|
+
export const REDIS_URL = env.string('REDIS_URL');
|
|
9
|
+
|
|
10
|
+
export default env;
|
|
11
|
+
export type Env = typeof env;
|
|
12
|
+
export {nodeEnv};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export class AuthenticationError extends Error {
|
|
2
|
+
public readonly code = 'AUTH_REQUIRED';
|
|
3
|
+
public readonly statusCode = 401;
|
|
4
|
+
public readonly data: Record<string, unknown> | undefined;
|
|
5
|
+
|
|
6
|
+
constructor(message: string, data?: Record<string, unknown>) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'AuthenticationError';
|
|
9
|
+
this.data = data;
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
12
|
+
if (Error.captureStackTrace) {
|
|
13
|
+
Error.captureStackTrace(this, AuthenticationError);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class TenantError extends Error {
|
|
19
|
+
public readonly code = 'TENANT_REQUIRED';
|
|
20
|
+
public readonly statusCode = 401;
|
|
21
|
+
public readonly data: Record<string, unknown> | undefined;
|
|
22
|
+
|
|
23
|
+
constructor(message: string, data?: Record<string, unknown>) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'TenantError';
|
|
26
|
+
this.data = data;
|
|
27
|
+
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
29
|
+
if (Error.captureStackTrace) {
|
|
30
|
+
Error.captureStackTrace(this, TenantError);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class PermissionError extends Error {
|
|
2
|
+
public readonly code = 'PERMISSION_DENIED';
|
|
3
|
+
public readonly statusCode = 403;
|
|
4
|
+
public readonly data: Record<string, unknown> | undefined;
|
|
5
|
+
|
|
6
|
+
constructor(message: string, data?: Record<string, unknown>) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'PermissionError';
|
|
9
|
+
this.data = data;
|
|
10
|
+
|
|
11
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
13
|
+
if (Error.captureStackTrace) {
|
|
14
|
+
Error.captureStackTrace(this, PermissionError);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example of how to use the cache middleware in a Moleculer service
|
|
3
|
+
* This file demonstrates various caching patterns and best practices
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {AppContext} from '../types';
|
|
7
|
+
import {CacheMiddleware} from '../middlewares/cache.middleware';
|
|
8
|
+
import {REDIS_URL} from '../env';
|
|
9
|
+
|
|
10
|
+
// Initialize cache on application start
|
|
11
|
+
export function initializeCache() {
|
|
12
|
+
CacheMiddleware.init({
|
|
13
|
+
redisUrl: REDIS_URL, // Falls back to in-memory if not set
|
|
14
|
+
ttl: 10 * 60 * 1000, // 10 minutes default TTL
|
|
15
|
+
namespace: 'app', // Namespace for cache keys
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Example 1: Simple get/set pattern
|
|
21
|
+
*/
|
|
22
|
+
export async function getUserWithCache(ctx: AppContext, userId: string) {
|
|
23
|
+
const cacheKey = `user:${userId}`;
|
|
24
|
+
|
|
25
|
+
// Try to get from cache
|
|
26
|
+
const cached = await ctx.cache.get(cacheKey);
|
|
27
|
+
if (cached) {
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Cache miss - fetch from database
|
|
32
|
+
const user = await fetchUserFromDatabase(userId);
|
|
33
|
+
|
|
34
|
+
// Store in cache for 5 minutes
|
|
35
|
+
await ctx.cache.set(cacheKey, user, 5 * 60 * 1000);
|
|
36
|
+
|
|
37
|
+
return user;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Example 2: Using the wrap pattern (recommended)
|
|
42
|
+
* Automatically handles get/set logic
|
|
43
|
+
*/
|
|
44
|
+
export async function getUserWithWrap(ctx: AppContext, userId: string) {
|
|
45
|
+
return ctx.cache.wrap(
|
|
46
|
+
`user:${userId}`,
|
|
47
|
+
async () => {
|
|
48
|
+
// This function only runs if cache miss
|
|
49
|
+
return fetchUserFromDatabase(userId);
|
|
50
|
+
},
|
|
51
|
+
5 * 60 * 1000 // TTL
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Example 3: Caching complex calculations
|
|
57
|
+
*/
|
|
58
|
+
export async function getUserPermissions(ctx: AppContext, userId: string) {
|
|
59
|
+
return ctx.cache.wrap(
|
|
60
|
+
`permissions:${userId}`,
|
|
61
|
+
async () => {
|
|
62
|
+
// Complex permission calculation
|
|
63
|
+
const user = await fetchUserFromDatabase(userId);
|
|
64
|
+
const roles = await fetchUserRoles(userId);
|
|
65
|
+
const permissions = calculatePermissions(user, roles);
|
|
66
|
+
return permissions;
|
|
67
|
+
},
|
|
68
|
+
10 * 60 * 1000 // Cache for 10 minutes
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Example 4: Batch operations
|
|
74
|
+
*/
|
|
75
|
+
export async function getUsersBatch(ctx: AppContext, userIds: string[]) {
|
|
76
|
+
// Generate cache keys
|
|
77
|
+
const cacheKeys = userIds.map((id) => `user:${id}`);
|
|
78
|
+
|
|
79
|
+
// Get all from cache at once
|
|
80
|
+
const cachedUsers = await ctx.cache.mget(...cacheKeys);
|
|
81
|
+
|
|
82
|
+
// Find missing users
|
|
83
|
+
const result: Array<Record<string, unknown>> = [];
|
|
84
|
+
const missingIds: string[] = [];
|
|
85
|
+
|
|
86
|
+
cachedUsers.forEach((user, index) => {
|
|
87
|
+
if (user !== undefined && user !== null) {
|
|
88
|
+
result[index] = user as Record<string, unknown>;
|
|
89
|
+
} else {
|
|
90
|
+
const userId = userIds[index];
|
|
91
|
+
if (userId !== undefined) {
|
|
92
|
+
missingIds.push(userId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Fetch missing users from database
|
|
98
|
+
if (missingIds.length > 0) {
|
|
99
|
+
const missingUsers = await fetchUsersFromDatabase(missingIds);
|
|
100
|
+
|
|
101
|
+
// Prepare batch cache update
|
|
102
|
+
const cacheEntries: Record<string, unknown> = {};
|
|
103
|
+
missingUsers.forEach((user) => {
|
|
104
|
+
cacheEntries[`user:${user.id}`] = user;
|
|
105
|
+
// Add to result
|
|
106
|
+
const index = userIds.indexOf(user.id);
|
|
107
|
+
if (index !== -1) {
|
|
108
|
+
result[index] = user;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Set all at once
|
|
113
|
+
await ctx.cache.mset(cacheEntries, 5 * 60 * 1000);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Example 5: Cache invalidation after update
|
|
121
|
+
*/
|
|
122
|
+
export async function updateUser(
|
|
123
|
+
ctx: AppContext,
|
|
124
|
+
userId: string,
|
|
125
|
+
updates: Record<string, unknown>
|
|
126
|
+
) {
|
|
127
|
+
// Update in database
|
|
128
|
+
const updatedUser = await updateUserInDatabase(userId, updates);
|
|
129
|
+
|
|
130
|
+
// Invalidate all related caches
|
|
131
|
+
await ctx.cache.mdel(
|
|
132
|
+
`user:${userId}`,
|
|
133
|
+
`permissions:${userId}`,
|
|
134
|
+
`profile:${userId}`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Optionally, pre-populate cache with new data
|
|
138
|
+
await ctx.cache.set(`user:${userId}`, updatedUser, 5 * 60 * 1000);
|
|
139
|
+
|
|
140
|
+
return updatedUser;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Example 6: Check if key exists
|
|
145
|
+
*/
|
|
146
|
+
export async function isUserCached(ctx: AppContext, userId: string) {
|
|
147
|
+
return ctx.cache.has(`user:${userId}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Example 7: Clear entire cache namespace
|
|
152
|
+
*/
|
|
153
|
+
export async function clearAllCache(ctx: AppContext) {
|
|
154
|
+
await ctx.cache.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Example 8: Cache with dynamic TTL based on data
|
|
159
|
+
*/
|
|
160
|
+
interface CacheableData {
|
|
161
|
+
type: 'static' | 'volatile' | 'normal';
|
|
162
|
+
[key: string]: unknown;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function cacheWithDynamicTTL(
|
|
166
|
+
ctx: AppContext,
|
|
167
|
+
key: string,
|
|
168
|
+
data: CacheableData
|
|
169
|
+
) {
|
|
170
|
+
// Determine TTL based on data characteristics
|
|
171
|
+
let ttl = 5 * 60 * 1000; // 5 minutes default
|
|
172
|
+
|
|
173
|
+
if (data.type === 'static') {
|
|
174
|
+
ttl = 60 * 60 * 1000; // 1 hour for static data
|
|
175
|
+
} else if (data.type === 'volatile') {
|
|
176
|
+
ttl = 60 * 1000; // 1 minute for volatile data
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await ctx.cache.set(key, data, ttl);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Example 9: Cache status check
|
|
184
|
+
*/
|
|
185
|
+
export async function checkCacheHealth(ctx: AppContext) {
|
|
186
|
+
const testKey = 'health:check';
|
|
187
|
+
const testValue = {timestamp: Date.now(), random: Math.random()};
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
// Test set
|
|
191
|
+
const setResult = await ctx.cache.set(testKey, testValue, 1000);
|
|
192
|
+
|
|
193
|
+
// Test get
|
|
194
|
+
const getResult = await ctx.cache.get(testKey);
|
|
195
|
+
|
|
196
|
+
// Test has
|
|
197
|
+
const hasResult = await ctx.cache.has(testKey);
|
|
198
|
+
|
|
199
|
+
// Test delete
|
|
200
|
+
const deleteResult = await ctx.cache.delete(testKey);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
healthy: true,
|
|
204
|
+
type: REDIS_URL ? 'Redis' : 'In-Memory',
|
|
205
|
+
operations: {
|
|
206
|
+
set: setResult,
|
|
207
|
+
get: JSON.stringify(getResult) === JSON.stringify(testValue),
|
|
208
|
+
has: hasResult,
|
|
209
|
+
delete: deleteResult,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
} catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
healthy: false,
|
|
215
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Mock database functions for example purposes
|
|
221
|
+
async function fetchUserFromDatabase(userId: string) {
|
|
222
|
+
return {
|
|
223
|
+
id: userId,
|
|
224
|
+
name: `User ${userId}`,
|
|
225
|
+
email: `user${userId}@example.com`,
|
|
226
|
+
createdAt: new Date(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function fetchUsersFromDatabase(userIds: string[]) {
|
|
231
|
+
return userIds.map((id) => ({
|
|
232
|
+
id,
|
|
233
|
+
name: `User ${id}`,
|
|
234
|
+
email: `user${id}@example.com`,
|
|
235
|
+
createdAt: new Date(),
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
240
|
+
async function fetchUserRoles(_userId: string) {
|
|
241
|
+
return ['user', 'member'];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
interface User {
|
|
245
|
+
id: string;
|
|
246
|
+
name: string;
|
|
247
|
+
email: string;
|
|
248
|
+
createdAt: Date;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function calculatePermissions(_user: User, roles: string[]) {
|
|
252
|
+
return {
|
|
253
|
+
canRead: true,
|
|
254
|
+
canWrite: roles.includes('admin'),
|
|
255
|
+
canDelete: roles.includes('admin'),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function updateUserInDatabase(
|
|
260
|
+
userId: string,
|
|
261
|
+
updates: Record<string, unknown>
|
|
262
|
+
) {
|
|
263
|
+
return {
|
|
264
|
+
id: userId,
|
|
265
|
+
...updates,
|
|
266
|
+
updatedAt: new Date(),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Usage in a Moleculer service:
|
|
272
|
+
*
|
|
273
|
+
* export default {
|
|
274
|
+
* name: 'users',
|
|
275
|
+
* middlewares: [CacheMiddleware],
|
|
276
|
+
*
|
|
277
|
+
* started() {
|
|
278
|
+
* initializeCache();
|
|
279
|
+
* },
|
|
280
|
+
*
|
|
281
|
+
* stopped() {
|
|
282
|
+
* return CacheMiddleware.stopped();
|
|
283
|
+
* },
|
|
284
|
+
*
|
|
285
|
+
* actions: {
|
|
286
|
+
* get: {
|
|
287
|
+
* async handler(ctx) {
|
|
288
|
+
* return getUserWithWrap(ctx, ctx.params.id);
|
|
289
|
+
* }
|
|
290
|
+
* }
|
|
291
|
+
* }
|
|
292
|
+
* }
|
|
293
|
+
*/
|