@exulu/backend 1.46.0 → 1.47.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/.agents/skills/mintlify/SKILL.md +347 -0
- package/.editorconfig +15 -0
- package/.eslintrc.json +52 -0
- package/.jscpd.json +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc.json +12 -0
- package/CHANGELOG.md +11 -4
- package/README.md +747 -0
- package/SECURITY.md +5 -0
- package/dist/index.cjs +12015 -10506
- package/dist/index.d.cts +725 -667
- package/dist/index.d.ts +725 -667
- package/dist/index.js +12034 -10518
- package/ee/LICENSE.md +62 -0
- package/ee/agentic-retrieval/index.ts +1109 -0
- package/ee/documents/THIRD_PARTY_LICENSES/docling.txt +31 -0
- package/ee/documents/processing/build_pdf_processor.sh +35 -0
- package/ee/documents/processing/chunk_markdown.py +263 -0
- package/ee/documents/processing/doc_processor.ts +635 -0
- package/ee/documents/processing/pdf_processor.spec +115 -0
- package/ee/documents/processing/pdf_to_markdown.py +420 -0
- package/ee/documents/processing/requirements.txt +4 -0
- package/ee/entitlements.ts +49 -0
- package/ee/markdown.ts +686 -0
- package/ee/queues/decorator.ts +140 -0
- package/ee/queues/queues.ts +156 -0
- package/ee/queues/server.ts +6 -0
- package/ee/rbac-resolver.ts +51 -0
- package/ee/rbac-update.ts +111 -0
- package/ee/schemas.ts +347 -0
- package/ee/tokenizer.ts +80 -0
- package/ee/workers.ts +1423 -0
- package/eslint.config.js +88 -0
- package/jest.config.ts +25 -0
- package/license.md +73 -49
- package/mintlify-docs/.mintignore +7 -0
- package/mintlify-docs/AGENTS.md +33 -0
- package/mintlify-docs/CLAUDE.MD +50 -0
- package/mintlify-docs/CONTRIBUTING.md +32 -0
- package/mintlify-docs/LICENSE +21 -0
- package/mintlify-docs/README.md +55 -0
- package/mintlify-docs/ai-tools/claude-code.mdx +43 -0
- package/mintlify-docs/ai-tools/cursor.mdx +39 -0
- package/mintlify-docs/ai-tools/windsurf.mdx +39 -0
- package/mintlify-docs/api-reference/core-types/agent-types.mdx +110 -0
- package/mintlify-docs/api-reference/core-types/analytics-types.mdx +95 -0
- package/mintlify-docs/api-reference/core-types/configuration-types.mdx +83 -0
- package/mintlify-docs/api-reference/core-types/evaluation-types.mdx +106 -0
- package/mintlify-docs/api-reference/core-types/job-types.mdx +135 -0
- package/mintlify-docs/api-reference/core-types/overview.mdx +73 -0
- package/mintlify-docs/api-reference/core-types/prompt-types.mdx +102 -0
- package/mintlify-docs/api-reference/core-types/rbac-types.mdx +163 -0
- package/mintlify-docs/api-reference/core-types/session-types.mdx +77 -0
- package/mintlify-docs/api-reference/core-types/user-management.mdx +112 -0
- package/mintlify-docs/api-reference/core-types/workflow-types.mdx +88 -0
- package/mintlify-docs/api-reference/core-types.mdx +585 -0
- package/mintlify-docs/api-reference/dynamic-types.mdx +851 -0
- package/mintlify-docs/api-reference/endpoint/create.mdx +4 -0
- package/mintlify-docs/api-reference/endpoint/delete.mdx +4 -0
- package/mintlify-docs/api-reference/endpoint/get.mdx +4 -0
- package/mintlify-docs/api-reference/endpoint/webhook.mdx +4 -0
- package/mintlify-docs/api-reference/introduction.mdx +661 -0
- package/mintlify-docs/api-reference/mutations.mdx +1012 -0
- package/mintlify-docs/api-reference/openapi.json +217 -0
- package/mintlify-docs/api-reference/queries.mdx +1154 -0
- package/mintlify-docs/backend/introduction.mdx +218 -0
- package/mintlify-docs/changelog.mdx +293 -0
- package/mintlify-docs/community-edition.mdx +304 -0
- package/mintlify-docs/core/exulu-agent/api-reference.mdx +894 -0
- package/mintlify-docs/core/exulu-agent/configuration.mdx +690 -0
- package/mintlify-docs/core/exulu-agent/introduction.mdx +552 -0
- package/mintlify-docs/core/exulu-app/api-reference.mdx +481 -0
- package/mintlify-docs/core/exulu-app/configuration.mdx +319 -0
- package/mintlify-docs/core/exulu-app/introduction.mdx +117 -0
- package/mintlify-docs/core/exulu-authentication.mdx +810 -0
- package/mintlify-docs/core/exulu-chunkers/api-reference.mdx +1011 -0
- package/mintlify-docs/core/exulu-chunkers/configuration.mdx +596 -0
- package/mintlify-docs/core/exulu-chunkers/introduction.mdx +403 -0
- package/mintlify-docs/core/exulu-context/api-reference.mdx +911 -0
- package/mintlify-docs/core/exulu-context/configuration.mdx +648 -0
- package/mintlify-docs/core/exulu-context/introduction.mdx +394 -0
- package/mintlify-docs/core/exulu-database.mdx +811 -0
- package/mintlify-docs/core/exulu-default-agents.mdx +545 -0
- package/mintlify-docs/core/exulu-eval/api-reference.mdx +772 -0
- package/mintlify-docs/core/exulu-eval/configuration.mdx +680 -0
- package/mintlify-docs/core/exulu-eval/introduction.mdx +459 -0
- package/mintlify-docs/core/exulu-logging.mdx +464 -0
- package/mintlify-docs/core/exulu-otel.mdx +670 -0
- package/mintlify-docs/core/exulu-queues/api-reference.mdx +648 -0
- package/mintlify-docs/core/exulu-queues/configuration.mdx +650 -0
- package/mintlify-docs/core/exulu-queues/introduction.mdx +474 -0
- package/mintlify-docs/core/exulu-reranker/api-reference.mdx +630 -0
- package/mintlify-docs/core/exulu-reranker/configuration.mdx +663 -0
- package/mintlify-docs/core/exulu-reranker/introduction.mdx +516 -0
- package/mintlify-docs/core/exulu-tool/api-reference.mdx +723 -0
- package/mintlify-docs/core/exulu-tool/configuration.mdx +805 -0
- package/mintlify-docs/core/exulu-tool/introduction.mdx +539 -0
- package/mintlify-docs/core/exulu-variables/api-reference.mdx +699 -0
- package/mintlify-docs/core/exulu-variables/configuration.mdx +736 -0
- package/mintlify-docs/core/exulu-variables/introduction.mdx +511 -0
- package/mintlify-docs/development.mdx +94 -0
- package/mintlify-docs/docs.json +248 -0
- package/mintlify-docs/enterprise-edition.mdx +538 -0
- package/mintlify-docs/essentials/code.mdx +35 -0
- package/mintlify-docs/essentials/images.mdx +59 -0
- package/mintlify-docs/essentials/markdown.mdx +88 -0
- package/mintlify-docs/essentials/navigation.mdx +87 -0
- package/mintlify-docs/essentials/reusable-snippets.mdx +110 -0
- package/mintlify-docs/essentials/settings.mdx +318 -0
- package/mintlify-docs/favicon.svg +3 -0
- package/mintlify-docs/frontend/introduction.mdx +39 -0
- package/mintlify-docs/getting-started.mdx +267 -0
- package/mintlify-docs/guides/custom-agent.mdx +608 -0
- package/mintlify-docs/guides/first-agent.mdx +315 -0
- package/mintlify-docs/images/admin_ui.png +0 -0
- package/mintlify-docs/images/contexts.png +0 -0
- package/mintlify-docs/images/create_agents.png +0 -0
- package/mintlify-docs/images/evals.png +0 -0
- package/mintlify-docs/images/graphql.png +0 -0
- package/mintlify-docs/images/graphql_api.png +0 -0
- package/mintlify-docs/images/hero-dark.png +0 -0
- package/mintlify-docs/images/hero-light.png +0 -0
- package/mintlify-docs/images/hero.png +0 -0
- package/mintlify-docs/images/knowledge_sources.png +0 -0
- package/mintlify-docs/images/mcp.png +0 -0
- package/mintlify-docs/images/scaling.png +0 -0
- package/mintlify-docs/index.mdx +411 -0
- package/mintlify-docs/logo/dark.svg +9 -0
- package/mintlify-docs/logo/light.svg +9 -0
- package/mintlify-docs/partners.mdx +558 -0
- package/mintlify-docs/products.mdx +77 -0
- package/mintlify-docs/snippets/snippet-intro.mdx +4 -0
- package/mintlify-docs/styles.css +207 -0
- package/{documentation → old-documentation}/logging.md +3 -3
- package/package.json +35 -4
- package/skills-lock.json +10 -0
- package/types/context-processor.ts +45 -0
- package/types/exulu-table-definition.ts +79 -0
- package/types/file-types.ts +18 -0
- package/types/models/agent.ts +10 -12
- package/types/models/exulu-agent-tool-config.ts +11 -0
- package/types/models/rate-limiter-rules.ts +7 -0
- package/types/provider-config.ts +21 -0
- package/types/queue-config.ts +16 -0
- package/types/rbac-rights-modes.ts +1 -0
- package/types/statistics.ts +20 -0
- package/types/workflow.ts +31 -0
- package/changelog-backend-10.11.2025_03.12.2025.md +0 -316
- package/types/models/agent-backend.ts +0 -15
- /package/{documentation → old-documentation}/otel.md +0 -0
- /package/{patch-older-releases-readme.md → old-documentation/patch-older-releases.md} +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Queue } from "bullmq";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import type { UIMessage } from "ai";
|
|
4
|
+
import type { STATISTICS_LABELS } from "@EXULU_TYPES/statistics";
|
|
5
|
+
|
|
6
|
+
type ExuluJobType = "embedder" | "workflow" | "eval" | "processor";
|
|
7
|
+
|
|
8
|
+
type ExuluBullMqDecoratorData = {
|
|
9
|
+
queue: Queue;
|
|
10
|
+
label: string;
|
|
11
|
+
embedder?: string;
|
|
12
|
+
processor?: string;
|
|
13
|
+
inputs: any;
|
|
14
|
+
user?: number;
|
|
15
|
+
role?: string;
|
|
16
|
+
trigger: STATISTICS_LABELS;
|
|
17
|
+
workflow?: string;
|
|
18
|
+
evaluation?: string;
|
|
19
|
+
item?: string;
|
|
20
|
+
context?: string;
|
|
21
|
+
retries?: number;
|
|
22
|
+
backoff?: {
|
|
23
|
+
type: "exponential" | "linear";
|
|
24
|
+
delay: number; // in milliseconds
|
|
25
|
+
};
|
|
26
|
+
timeoutInSeconds: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type BullMqJobData = {
|
|
30
|
+
label: string;
|
|
31
|
+
type: string;
|
|
32
|
+
source?: string;
|
|
33
|
+
inputs: any;
|
|
34
|
+
timeoutInSeconds: number;
|
|
35
|
+
user?: number;
|
|
36
|
+
role?: string;
|
|
37
|
+
trigger: STATISTICS_LABELS;
|
|
38
|
+
messages?: UIMessage[];
|
|
39
|
+
eval_run_id?: string;
|
|
40
|
+
eval_run_name?: string;
|
|
41
|
+
test_case_id?: string;
|
|
42
|
+
test_case_name?: string;
|
|
43
|
+
eval_functions?: {
|
|
44
|
+
id: string;
|
|
45
|
+
config: Record<string, any>;
|
|
46
|
+
}[];
|
|
47
|
+
agent_id?: string;
|
|
48
|
+
expected_output?: string;
|
|
49
|
+
expected_tools?: string[];
|
|
50
|
+
expected_knowledge_sources?: string[];
|
|
51
|
+
expected_agent_tools?: string[];
|
|
52
|
+
config?: Record<string, any>;
|
|
53
|
+
scoring_method?: string;
|
|
54
|
+
pass_threshold?: number;
|
|
55
|
+
workflow?: string;
|
|
56
|
+
embedder?: string;
|
|
57
|
+
processor?: string;
|
|
58
|
+
evaluation?: string;
|
|
59
|
+
item?: string;
|
|
60
|
+
context?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const bullmqDecorator = async ({
|
|
64
|
+
queue,
|
|
65
|
+
label,
|
|
66
|
+
embedder,
|
|
67
|
+
processor,
|
|
68
|
+
inputs,
|
|
69
|
+
evaluation,
|
|
70
|
+
user,
|
|
71
|
+
role,
|
|
72
|
+
trigger,
|
|
73
|
+
workflow,
|
|
74
|
+
item,
|
|
75
|
+
context,
|
|
76
|
+
retries,
|
|
77
|
+
backoff,
|
|
78
|
+
timeoutInSeconds,
|
|
79
|
+
}: ExuluBullMqDecoratorData) => {
|
|
80
|
+
const types = [embedder, workflow, processor, evaluation];
|
|
81
|
+
|
|
82
|
+
if (types.filter((type) => type).length > 1) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
"Cannot have multiple types in the same job, must be one of the following: embedder, workflow, processor or eval.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let type: ExuluJobType = "embedder";
|
|
89
|
+
|
|
90
|
+
if (workflow) {
|
|
91
|
+
type = "workflow";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (processor) {
|
|
95
|
+
type = "processor";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (evaluation) {
|
|
99
|
+
type = "eval";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (embedder) {
|
|
103
|
+
type = "embedder";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const jobData: BullMqJobData = {
|
|
107
|
+
label,
|
|
108
|
+
type: `${type}`,
|
|
109
|
+
timeoutInSeconds: timeoutInSeconds || 180, // 3 minutes default
|
|
110
|
+
inputs,
|
|
111
|
+
...(user && { user }),
|
|
112
|
+
...(role && { role }),
|
|
113
|
+
...(trigger && { trigger }),
|
|
114
|
+
...(workflow && { workflow }),
|
|
115
|
+
...(embedder && { embedder }),
|
|
116
|
+
...(processor && { processor }),
|
|
117
|
+
...(evaluation && { evaluation }),
|
|
118
|
+
...(item && { item }),
|
|
119
|
+
...(context && { context }),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const redisId = uuidv4();
|
|
123
|
+
const job = await queue.add(`${embedder || workflow || processor || evaluation}`, jobData, {
|
|
124
|
+
jobId: redisId,
|
|
125
|
+
// Setting it to 3 as a sensible default, as
|
|
126
|
+
// many AI services are quite unstable.
|
|
127
|
+
attempts: retries || 3, // todo make this configurable?
|
|
128
|
+
removeOnComplete: 5000,
|
|
129
|
+
removeOnFail: 10000,
|
|
130
|
+
backoff: backoff || {
|
|
131
|
+
type: "exponential",
|
|
132
|
+
delay: 2000,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...job,
|
|
138
|
+
redis: job.id,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Queue } from "bullmq";
|
|
2
|
+
import { redisServer } from "./server";
|
|
3
|
+
import { BullMQOtel } from "bullmq-otel";
|
|
4
|
+
import type { ExuluQueueConfig } from "@EXULU_TYPES/queue-config";
|
|
5
|
+
import { checkLicense } from "@EE/entitlements";
|
|
6
|
+
|
|
7
|
+
// Used for workflows and embedders
|
|
8
|
+
class ExuluQueues {
|
|
9
|
+
queues: {
|
|
10
|
+
queue: Queue;
|
|
11
|
+
ratelimit: number;
|
|
12
|
+
concurrency: {
|
|
13
|
+
worker: number;
|
|
14
|
+
queue: number;
|
|
15
|
+
};
|
|
16
|
+
timeoutInSeconds: number;
|
|
17
|
+
}[];
|
|
18
|
+
constructor() {
|
|
19
|
+
this.queues = [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public list: Map<
|
|
23
|
+
string,
|
|
24
|
+
{
|
|
25
|
+
name: string;
|
|
26
|
+
concurrency: {
|
|
27
|
+
worker: number;
|
|
28
|
+
queue: number;
|
|
29
|
+
};
|
|
30
|
+
ratelimit: number;
|
|
31
|
+
timeoutInSeconds: number;
|
|
32
|
+
use: () => Promise<ExuluQueueConfig>;
|
|
33
|
+
}
|
|
34
|
+
> = new Map(); // list of queue names
|
|
35
|
+
|
|
36
|
+
queue(name: string):
|
|
37
|
+
| {
|
|
38
|
+
queue: Queue;
|
|
39
|
+
ratelimit: number;
|
|
40
|
+
concurrency: {
|
|
41
|
+
worker: number;
|
|
42
|
+
queue: number;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
| undefined {
|
|
46
|
+
return this.queues.find((x) => x.queue?.name === name) as
|
|
47
|
+
| {
|
|
48
|
+
queue: Queue;
|
|
49
|
+
ratelimit: number;
|
|
50
|
+
concurrency: {
|
|
51
|
+
worker: number;
|
|
52
|
+
queue: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
| undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// name: string
|
|
59
|
+
// concurrency: global concurrency for the queue
|
|
60
|
+
// ratelimit: maximum number of jobs per second
|
|
61
|
+
// Rate limit is set on workers (see workers.ts), even global rate limits,
|
|
62
|
+
// that is a bit counter-intuitive. Since queues are registered using .user
|
|
63
|
+
// method of ExuluQueues we need to store the desired rate limit on the queue
|
|
64
|
+
// here so we can use the value when creating workers for the queue instance
|
|
65
|
+
// as there is no way to store a rate limit value natively on a bullm queue.
|
|
66
|
+
register = (
|
|
67
|
+
name: string,
|
|
68
|
+
concurrency: {
|
|
69
|
+
worker: number;
|
|
70
|
+
queue: number;
|
|
71
|
+
},
|
|
72
|
+
ratelimit: number = 1,
|
|
73
|
+
timeoutInSeconds: number = 180,
|
|
74
|
+
): {
|
|
75
|
+
use: () => Promise<ExuluQueueConfig>;
|
|
76
|
+
} => {
|
|
77
|
+
const license = checkLicense();
|
|
78
|
+
if (!license["queues"]) {
|
|
79
|
+
throw new Error(`[EXULU] You are not licensed to use queues so cannot register a queue. Please set your EXULU_ENTERPRISE_LICENSE env variable.`);
|
|
80
|
+
}
|
|
81
|
+
const queueConcurrency = concurrency.queue || 1;
|
|
82
|
+
const workerConcurrency = concurrency.worker || 1;
|
|
83
|
+
|
|
84
|
+
const use = async (): Promise<ExuluQueueConfig> => {
|
|
85
|
+
const existing = this.queues.find((x) => x.queue?.name === name);
|
|
86
|
+
if (existing) {
|
|
87
|
+
const globalConcurrency = await existing.queue.getGlobalConcurrency();
|
|
88
|
+
if (globalConcurrency !== queueConcurrency) {
|
|
89
|
+
await existing.queue.setGlobalConcurrency(queueConcurrency);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
queue: existing.queue,
|
|
93
|
+
ratelimit,
|
|
94
|
+
concurrency: {
|
|
95
|
+
worker: workerConcurrency,
|
|
96
|
+
queue: queueConcurrency,
|
|
97
|
+
},
|
|
98
|
+
timeoutInSeconds,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!redisServer.host?.length || !redisServer.port?.length) {
|
|
103
|
+
console.error(
|
|
104
|
+
`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or embedder (look for ExuluQueues.register().use() ).`,
|
|
105
|
+
);
|
|
106
|
+
console.error("Stack trace:");
|
|
107
|
+
console.trace();
|
|
108
|
+
throw new Error(`[EXULU] no redis server configured.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const newQueue = new Queue(`${name}`, {
|
|
112
|
+
connection: {
|
|
113
|
+
...redisServer,
|
|
114
|
+
enableOfflineQueue: false,
|
|
115
|
+
},
|
|
116
|
+
telemetry: new BullMQOtel("simple-guide"),
|
|
117
|
+
});
|
|
118
|
+
await newQueue.setGlobalConcurrency(queueConcurrency);
|
|
119
|
+
this.queues.push({
|
|
120
|
+
queue: newQueue,
|
|
121
|
+
ratelimit,
|
|
122
|
+
concurrency: {
|
|
123
|
+
worker: workerConcurrency,
|
|
124
|
+
queue: queueConcurrency,
|
|
125
|
+
},
|
|
126
|
+
timeoutInSeconds,
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
queue: newQueue,
|
|
130
|
+
ratelimit,
|
|
131
|
+
concurrency: {
|
|
132
|
+
worker: workerConcurrency,
|
|
133
|
+
queue: queueConcurrency,
|
|
134
|
+
},
|
|
135
|
+
timeoutInSeconds,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.list.set(name, {
|
|
140
|
+
name,
|
|
141
|
+
concurrency: {
|
|
142
|
+
worker: workerConcurrency,
|
|
143
|
+
queue: queueConcurrency,
|
|
144
|
+
},
|
|
145
|
+
ratelimit,
|
|
146
|
+
timeoutInSeconds,
|
|
147
|
+
use,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
use,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const queues = new ExuluQueues();
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { checkLicense } from "./entitlements";
|
|
2
|
+
|
|
3
|
+
export const RBACResolver = async (
|
|
4
|
+
db: any,
|
|
5
|
+
entityName: string,
|
|
6
|
+
resourceId: string,
|
|
7
|
+
rights_mode: string,
|
|
8
|
+
): Promise<{
|
|
9
|
+
type: string;
|
|
10
|
+
users: any[];
|
|
11
|
+
roles: any[];
|
|
12
|
+
}> => {
|
|
13
|
+
|
|
14
|
+
// If RBAC is not available
|
|
15
|
+
// the system defaults to public.
|
|
16
|
+
const license = checkLicense()
|
|
17
|
+
if (!license.rbac) {
|
|
18
|
+
return {
|
|
19
|
+
type: "public",
|
|
20
|
+
users: [],
|
|
21
|
+
roles: []
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Get RBAC records for this resource
|
|
25
|
+
const rbacRecords = await db
|
|
26
|
+
.from("rbac")
|
|
27
|
+
.where({
|
|
28
|
+
entity: entityName,
|
|
29
|
+
target_resource_id: resourceId,
|
|
30
|
+
})
|
|
31
|
+
.select("*");
|
|
32
|
+
|
|
33
|
+
const users = rbacRecords
|
|
34
|
+
.filter((r) => r.access_type === "User")
|
|
35
|
+
?.map((r) => ({ id: r.user_id, rights: r.rights }));
|
|
36
|
+
|
|
37
|
+
const roles = rbacRecords
|
|
38
|
+
.filter((r) => r.access_type === "Role")
|
|
39
|
+
?.map((r) => ({ id: r.role_id, rights: r.rights }));
|
|
40
|
+
|
|
41
|
+
// Determine the type based on rights_mode or presence of records
|
|
42
|
+
let type = rights_mode || "private";
|
|
43
|
+
if (type === "users" && users.length === 0) type = "private";
|
|
44
|
+
if (type === "roles" && roles.length === 0) type = "private";
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
type,
|
|
48
|
+
users,
|
|
49
|
+
roles,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Helper function to handle RBAC updates
|
|
2
|
+
import { checkLicense } from "./entitlements.ts";
|
|
3
|
+
|
|
4
|
+
export const handleRBACUpdate = async (
|
|
5
|
+
db: any,
|
|
6
|
+
entityName: string,
|
|
7
|
+
resourceId: string,
|
|
8
|
+
rbacData: any,
|
|
9
|
+
existingRbacRecords: any[],
|
|
10
|
+
): Promise<void> => {
|
|
11
|
+
|
|
12
|
+
const license = checkLicense()
|
|
13
|
+
if (!license.rbac) {
|
|
14
|
+
console.warn(`[EXULU] You are not licensed to use RBAC.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { users = [], roles = [] /* projects = [] */ } = rbacData;
|
|
19
|
+
|
|
20
|
+
// Get existing RBAC records if not provided
|
|
21
|
+
if (!existingRbacRecords) {
|
|
22
|
+
existingRbacRecords = await db
|
|
23
|
+
.from("rbac")
|
|
24
|
+
.where({
|
|
25
|
+
entity: entityName,
|
|
26
|
+
target_resource_id: resourceId,
|
|
27
|
+
})
|
|
28
|
+
.select("*");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create sets for comparison
|
|
32
|
+
const newUserRecords = new Set(users.map((u: any) => `${u.id}:${u.rights}`));
|
|
33
|
+
const newRoleRecords = new Set(roles.map((r: any) => `${r.id}:${r.rights}`));
|
|
34
|
+
// const newProjectRecords = new Set(projects.map((p: any) => `${p.id}:${p.rights}`));
|
|
35
|
+
const existingUserRecords = new Set(
|
|
36
|
+
existingRbacRecords
|
|
37
|
+
.filter((r) => r.access_type === "User")
|
|
38
|
+
.map((r) => `${r.user_id}:${r.rights}`),
|
|
39
|
+
);
|
|
40
|
+
const existingRoleRecords = new Set(
|
|
41
|
+
existingRbacRecords
|
|
42
|
+
.filter((r) => r.access_type === "Role")
|
|
43
|
+
.map((r) => `${r.role_id}:${r.rights}`),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Records to create
|
|
47
|
+
const usersToCreate = users.filter((u: any) => !existingUserRecords.has(`${u.id}:${u.rights}`));
|
|
48
|
+
const rolesToCreate = roles.filter((r: any) => !existingRoleRecords.has(`${r.id}:${r.rights}`));
|
|
49
|
+
// const projectsToCreate = projects.filter((p: any) => !existingProjectRecords.has(`${p.id}:${p.rights}`));
|
|
50
|
+
|
|
51
|
+
// Records to remove
|
|
52
|
+
const usersToRemove = existingRbacRecords.filter(
|
|
53
|
+
(r) => r.access_type === "User" && !newUserRecords.has(`${r.user_id}:${r.rights}`),
|
|
54
|
+
);
|
|
55
|
+
const rolesToRemove = existingRbacRecords.filter(
|
|
56
|
+
(r) => r.access_type === "Role" && !newRoleRecords.has(`${r.role_id}:${r.rights}`),
|
|
57
|
+
);
|
|
58
|
+
// const projectsToRemove = existingRbacRecords
|
|
59
|
+
// .filter(r => r.access_type === 'Project' && !newProjectRecords.has(`${r.project_id}:${r.rights}`));
|
|
60
|
+
|
|
61
|
+
// Remove obsolete records
|
|
62
|
+
if (usersToRemove.length > 0) {
|
|
63
|
+
await db
|
|
64
|
+
.from("rbac")
|
|
65
|
+
.whereIn(
|
|
66
|
+
"id",
|
|
67
|
+
usersToRemove.map((r) => r.id),
|
|
68
|
+
)
|
|
69
|
+
.del();
|
|
70
|
+
}
|
|
71
|
+
if (rolesToRemove.length > 0) {
|
|
72
|
+
await db
|
|
73
|
+
.from("rbac")
|
|
74
|
+
.whereIn(
|
|
75
|
+
"id",
|
|
76
|
+
rolesToRemove.map((r) => r.id),
|
|
77
|
+
)
|
|
78
|
+
.del();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Create new records
|
|
82
|
+
const recordsToInsert: any[] = [];
|
|
83
|
+
|
|
84
|
+
usersToCreate.forEach((user: any) => {
|
|
85
|
+
recordsToInsert.push({
|
|
86
|
+
entity: entityName,
|
|
87
|
+
access_type: "User",
|
|
88
|
+
target_resource_id: resourceId,
|
|
89
|
+
user_id: user.id,
|
|
90
|
+
rights: user.rights,
|
|
91
|
+
createdAt: new Date(),
|
|
92
|
+
updatedAt: new Date(),
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
rolesToCreate.forEach((role: any) => {
|
|
97
|
+
recordsToInsert.push({
|
|
98
|
+
entity: entityName,
|
|
99
|
+
access_type: "Role",
|
|
100
|
+
target_resource_id: resourceId,
|
|
101
|
+
role_id: role.id,
|
|
102
|
+
rights: role.rights,
|
|
103
|
+
createdAt: new Date(),
|
|
104
|
+
updatedAt: new Date(),
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (recordsToInsert.length > 0) {
|
|
109
|
+
await db.from("rbac").insert(recordsToInsert);
|
|
110
|
+
}
|
|
111
|
+
};
|