@realtimex/folio 0.1.15 → 0.1.17
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/api/src/middleware/auth.ts +77 -0
- package/api/src/routes/chat.ts +7 -1
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/ingestions.ts +51 -6
- package/api/src/routes/policies.ts +50 -7
- package/api/src/routes/stats.ts +9 -5
- package/api/src/routes/workspaces.ts +290 -0
- package/api/src/services/ChatService.ts +8 -2
- package/api/src/services/IngestionService.ts +38 -26
- package/api/src/services/PolicyEngine.ts +4 -1
- package/api/src/services/PolicyLearningService.ts +31 -6
- package/api/src/services/PolicyLoader.ts +44 -25
- package/api/src/services/RAGService.ts +52 -12
- package/api/src/services/SDKService.ts +48 -2
- package/dist/api/src/middleware/auth.js +59 -0
- package/dist/api/src/routes/chat.js +1 -1
- package/dist/api/src/routes/index.js +2 -0
- package/dist/api/src/routes/ingestions.js +51 -9
- package/dist/api/src/routes/policies.js +49 -7
- package/dist/api/src/routes/stats.js +9 -5
- package/dist/api/src/routes/workspaces.js +220 -0
- package/dist/api/src/services/ChatService.js +7 -2
- package/dist/api/src/services/IngestionService.js +35 -30
- package/dist/api/src/services/PolicyEngine.js +2 -1
- package/dist/api/src/services/PolicyLearningService.js +28 -6
- package/dist/api/src/services/PolicyLoader.js +29 -25
- package/dist/api/src/services/RAGService.js +43 -11
- package/dist/api/src/services/SDKService.js +37 -2
- package/dist/assets/index-CTn5FcC4.js +113 -0
- package/dist/assets/index-Dq9sxoZK.css +1 -0
- package/dist/index.html +2 -2
- package/docs-dev/ingestion-engine.md +3 -3
- package/package.json +1 -1
- package/supabase/functions/workspace-invite/index.ts +110 -0
- package/supabase/migrations/20260223000000_initial_foundation.sql +5 -0
- package/supabase/migrations/20260224000004_add_avatars_storage.sql +4 -0
- package/supabase/migrations/20260224000006_add_policies_table.sql +5 -0
- package/supabase/migrations/20260224000008_add_ingestions_table.sql +2 -0
- package/supabase/migrations/20260225000000_setup_compatible_mode.sql +17 -4
- package/supabase/migrations/20260225000003_add_baseline_configs.sql +4 -3
- package/supabase/migrations/20260226000000_add_processing_events.sql +1 -0
- package/supabase/migrations/20260226000002_add_dynamic_rag.sql +1 -0
- package/supabase/migrations/20260226000005_add_chat_tables.sql +3 -0
- package/supabase/migrations/20260228000001_add_policy_match_feedback.sql +4 -0
- package/supabase/migrations/20260302064608_add_ingestion_llm_settings_compat.sql +15 -0
- package/supabase/migrations/20260303000000_add_workspaces_phase1.sql +459 -0
- package/supabase/migrations/20260303010000_add_workspace_management_rpc.sql +310 -0
- package/supabase/migrations/20260303020000_workspace_scope_document_chunks.sql +139 -0
- package/dist/assets/index-Cj989Mcp.js +0 -113
- package/dist/assets/index-DzN8-j-e.css +0 -1
|
@@ -13,10 +13,19 @@ declare global {
|
|
|
13
13
|
interface Request {
|
|
14
14
|
user?: User;
|
|
15
15
|
supabase?: SupabaseClient;
|
|
16
|
+
workspaceId?: string;
|
|
17
|
+
workspaceRole?: string;
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
type WorkspaceMembershipRow = {
|
|
23
|
+
workspace_id: string;
|
|
24
|
+
role: "owner" | "admin" | "member";
|
|
25
|
+
status: "active" | "invited" | "disabled";
|
|
26
|
+
created_at: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
20
29
|
function resolveSupabaseConfig(req: Request): { url: string; anonKey: string } | null {
|
|
21
30
|
const headerConfig = getSupabaseConfigFromHeaders(req.headers as Record<string, unknown>);
|
|
22
31
|
|
|
@@ -31,6 +40,64 @@ function resolveSupabaseConfig(req: Request): { url: string; anonKey: string } |
|
|
|
31
40
|
return headerConfig;
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
function resolvePreferredWorkspaceId(req: Request): string | null {
|
|
44
|
+
const raw = req.headers["x-workspace-id"];
|
|
45
|
+
if (typeof raw === "string") {
|
|
46
|
+
const trimmed = raw.trim();
|
|
47
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(raw) && typeof raw[0] === "string") {
|
|
50
|
+
const trimmed = raw[0].trim();
|
|
51
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function resolveWorkspaceContext(
|
|
57
|
+
req: Request,
|
|
58
|
+
supabase: SupabaseClient,
|
|
59
|
+
user: User
|
|
60
|
+
): Promise<{ workspaceId: string; workspaceRole: "owner" | "admin" | "member" } | null> {
|
|
61
|
+
const preferredWorkspaceId = resolvePreferredWorkspaceId(req);
|
|
62
|
+
const { data, error } = await supabase
|
|
63
|
+
.from("workspace_members")
|
|
64
|
+
.select("workspace_id,role,status,created_at")
|
|
65
|
+
.eq("user_id", user.id)
|
|
66
|
+
.eq("status", "active")
|
|
67
|
+
.order("created_at", { ascending: true });
|
|
68
|
+
|
|
69
|
+
if (error) {
|
|
70
|
+
const errorCode = (error as { code?: string }).code;
|
|
71
|
+
// Backward compatibility: allow projects that haven't migrated yet.
|
|
72
|
+
if (errorCode === "42P01") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
throw new AuthorizationError(`Failed to resolve workspace membership: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const memberships = (data ?? []) as WorkspaceMembershipRow[];
|
|
79
|
+
if (memberships.length === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (preferredWorkspaceId) {
|
|
84
|
+
const preferred = memberships.find((membership) => membership.workspace_id === preferredWorkspaceId);
|
|
85
|
+
if (preferred) {
|
|
86
|
+
return {
|
|
87
|
+
workspaceId: preferred.workspace_id,
|
|
88
|
+
workspaceRole: preferred.role,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const active = memberships[0];
|
|
94
|
+
if (!active) return null;
|
|
95
|
+
return {
|
|
96
|
+
workspaceId: active.workspace_id,
|
|
97
|
+
workspaceRole: active.role,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
34
101
|
export async function authMiddleware(req: Request, _res: Response, next: NextFunction): Promise<void> {
|
|
35
102
|
try {
|
|
36
103
|
const supabaseConfig = resolveSupabaseConfig(req);
|
|
@@ -60,6 +127,11 @@ export async function authMiddleware(req: Request, _res: Response, next: NextFun
|
|
|
60
127
|
|
|
61
128
|
req.user = user;
|
|
62
129
|
req.supabase = supabase;
|
|
130
|
+
const workspace = await resolveWorkspaceContext(req, supabase, user);
|
|
131
|
+
if (workspace) {
|
|
132
|
+
req.workspaceId = workspace.workspaceId;
|
|
133
|
+
req.workspaceRole = workspace.workspaceRole;
|
|
134
|
+
}
|
|
63
135
|
Logger.setPersistence(supabase, user.id);
|
|
64
136
|
return next();
|
|
65
137
|
}
|
|
@@ -90,6 +162,11 @@ export async function authMiddleware(req: Request, _res: Response, next: NextFun
|
|
|
90
162
|
|
|
91
163
|
req.user = user;
|
|
92
164
|
req.supabase = supabase;
|
|
165
|
+
const workspace = await resolveWorkspaceContext(req, supabase, user);
|
|
166
|
+
if (workspace) {
|
|
167
|
+
req.workspaceId = workspace.workspaceId;
|
|
168
|
+
req.workspaceRole = workspace.workspaceRole;
|
|
169
|
+
}
|
|
93
170
|
Logger.setPersistence(supabase, user.id);
|
|
94
171
|
next();
|
|
95
172
|
} catch (error) {
|
package/api/src/routes/chat.ts
CHANGED
|
@@ -142,7 +142,13 @@ router.post(
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
try {
|
|
145
|
-
const aiMessage = await ChatService.handleMessage(
|
|
145
|
+
const aiMessage = await ChatService.handleMessage(
|
|
146
|
+
normalizedSessionId,
|
|
147
|
+
req.user.id,
|
|
148
|
+
trimmedContent,
|
|
149
|
+
req.supabase,
|
|
150
|
+
req.workspaceId
|
|
151
|
+
);
|
|
146
152
|
res.json({ success: true, message: aiMessage });
|
|
147
153
|
} catch (error) {
|
|
148
154
|
const message = error instanceof Error ? error.message : "Failed to process message";
|
package/api/src/routes/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import settingsRoutes from "./settings.js";
|
|
|
14
14
|
import rulesRoutes from "./rules.js";
|
|
15
15
|
import chatRoutes from "./chat.js";
|
|
16
16
|
import statsRoutes from "./stats.js";
|
|
17
|
+
import workspaceRoutes from "./workspaces.js";
|
|
17
18
|
|
|
18
19
|
const router = Router();
|
|
19
20
|
|
|
@@ -31,5 +32,6 @@ router.use("/settings", settingsRoutes);
|
|
|
31
32
|
router.use("/rules", rulesRoutes);
|
|
32
33
|
router.use("/chat", chatRoutes);
|
|
33
34
|
router.use("/stats", statsRoutes);
|
|
35
|
+
router.use("/workspaces", workspaceRoutes);
|
|
34
36
|
|
|
35
37
|
export default router;
|
|
@@ -7,6 +7,7 @@ import crypto from "crypto";
|
|
|
7
7
|
import { asyncHandler } from "../middleware/errorHandler.js";
|
|
8
8
|
import { optionalAuth } from "../middleware/auth.js";
|
|
9
9
|
import { IngestionService } from "../services/IngestionService.js";
|
|
10
|
+
import { SDKService } from "../services/SDKService.js";
|
|
10
11
|
|
|
11
12
|
const router = Router();
|
|
12
13
|
const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 20 * 1024 * 1024 } });
|
|
@@ -21,10 +22,14 @@ router.get(
|
|
|
21
22
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
25
|
+
if (!req.workspaceId) {
|
|
26
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
24
29
|
const page = Math.max(1, parseInt(req.query["page"] as string) || 1);
|
|
25
30
|
const pageSize = Math.min(100, Math.max(1, parseInt(req.query["pageSize"] as string) || 20));
|
|
26
31
|
const query = (req.query["q"] as string | undefined)?.trim() || undefined;
|
|
27
|
-
const { ingestions, total } = await IngestionService.list(req.supabase, req.
|
|
32
|
+
const { ingestions, total } = await IngestionService.list(req.supabase, req.workspaceId, { page, pageSize, query });
|
|
28
33
|
res.json({ success: true, ingestions, total, page, pageSize });
|
|
29
34
|
})
|
|
30
35
|
);
|
|
@@ -37,7 +42,11 @@ router.get(
|
|
|
37
42
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
38
43
|
return;
|
|
39
44
|
}
|
|
40
|
-
|
|
45
|
+
if (!req.workspaceId) {
|
|
46
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const ingestion = await IngestionService.get(req.params["id"] as string, req.supabase, req.workspaceId);
|
|
41
50
|
if (!ingestion) {
|
|
42
51
|
res.status(404).json({ success: false, error: "Not found" });
|
|
43
52
|
return;
|
|
@@ -55,6 +64,10 @@ router.post(
|
|
|
55
64
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
67
|
+
if (!req.workspaceId) {
|
|
68
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
58
71
|
const file = req.file;
|
|
59
72
|
if (!file) {
|
|
60
73
|
res.status(400).json({ success: false, error: "No file uploaded" });
|
|
@@ -68,7 +81,11 @@ router.post(
|
|
|
68
81
|
.eq("user_id", req.user.id)
|
|
69
82
|
.maybeSingle();
|
|
70
83
|
|
|
71
|
-
const
|
|
84
|
+
const configuredStoragePath = typeof settings?.storage_path === "string" ? settings.storage_path.trim() : "";
|
|
85
|
+
const legacyDefaultDropzoneDir = path.join(os.homedir(), ".realtimex", "folio", "dropzone");
|
|
86
|
+
const dropzoneDir = !configuredStoragePath || configuredStoragePath === legacyDefaultDropzoneDir
|
|
87
|
+
? await SDKService.getDefaultDropzoneDir()
|
|
88
|
+
: configuredStoragePath;
|
|
72
89
|
await fs.mkdir(dropzoneDir, { recursive: true });
|
|
73
90
|
|
|
74
91
|
// Compute SHA-256 hash before writing — used for deduplication
|
|
@@ -85,6 +102,7 @@ router.post(
|
|
|
85
102
|
const ingestion = await IngestionService.ingest({
|
|
86
103
|
supabase: req.supabase,
|
|
87
104
|
userId: req.user.id,
|
|
105
|
+
workspaceId: req.workspaceId,
|
|
88
106
|
filename: file.originalname,
|
|
89
107
|
mimeType: file.mimetype,
|
|
90
108
|
fileSize: file.size,
|
|
@@ -106,7 +124,11 @@ router.post(
|
|
|
106
124
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
107
125
|
return;
|
|
108
126
|
}
|
|
109
|
-
|
|
127
|
+
if (!req.workspaceId) {
|
|
128
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const matched = await IngestionService.rerun(req.params["id"] as string, req.supabase, req.user.id, req.workspaceId);
|
|
110
132
|
res.json({ success: true, matched });
|
|
111
133
|
})
|
|
112
134
|
);
|
|
@@ -119,6 +141,10 @@ router.post(
|
|
|
119
141
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
120
142
|
return;
|
|
121
143
|
}
|
|
144
|
+
if (!req.workspaceId) {
|
|
145
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
122
148
|
|
|
123
149
|
const policyId = typeof req.body?.policy_id === "string" ? req.body.policy_id.trim() : "";
|
|
124
150
|
if (!policyId) {
|
|
@@ -135,6 +161,7 @@ router.post(
|
|
|
135
161
|
policyId,
|
|
136
162
|
req.supabase,
|
|
137
163
|
req.user.id,
|
|
164
|
+
req.workspaceId,
|
|
138
165
|
{
|
|
139
166
|
learn,
|
|
140
167
|
rerun,
|
|
@@ -165,6 +192,10 @@ router.post(
|
|
|
165
192
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
166
193
|
return;
|
|
167
194
|
}
|
|
195
|
+
if (!req.workspaceId) {
|
|
196
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
168
199
|
|
|
169
200
|
const policyId = typeof req.body?.policy_id === "string" ? req.body.policy_id.trim() : "";
|
|
170
201
|
if (!policyId) {
|
|
@@ -178,6 +209,7 @@ router.post(
|
|
|
178
209
|
policyId,
|
|
179
210
|
req.supabase,
|
|
180
211
|
req.user.id,
|
|
212
|
+
req.workspaceId,
|
|
181
213
|
{
|
|
182
214
|
provider: typeof req.body?.provider === "string" ? req.body.provider : undefined,
|
|
183
215
|
model: typeof req.body?.model === "string" ? req.body.model : undefined,
|
|
@@ -207,6 +239,10 @@ router.post(
|
|
|
207
239
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
208
240
|
return;
|
|
209
241
|
}
|
|
242
|
+
if (!req.workspaceId) {
|
|
243
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
210
246
|
const { data: settingsRow } = await req.supabase
|
|
211
247
|
.from("user_settings")
|
|
212
248
|
.select("llm_provider, llm_model, ingestion_llm_provider, ingestion_llm_model")
|
|
@@ -219,6 +255,7 @@ router.post(
|
|
|
219
255
|
req.params["id"] as string,
|
|
220
256
|
req.supabase,
|
|
221
257
|
req.user.id,
|
|
258
|
+
req.workspaceId,
|
|
222
259
|
llmSettings
|
|
223
260
|
);
|
|
224
261
|
res.json({ success: true, summary });
|
|
@@ -233,6 +270,10 @@ router.patch(
|
|
|
233
270
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
234
271
|
return;
|
|
235
272
|
}
|
|
273
|
+
if (!req.workspaceId) {
|
|
274
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
236
277
|
const tags: unknown = req.body?.tags;
|
|
237
278
|
if (!Array.isArray(tags) || tags.some((t) => typeof t !== "string")) {
|
|
238
279
|
res.status(400).json({ success: false, error: "tags must be an array of strings" });
|
|
@@ -243,7 +284,7 @@ router.patch(
|
|
|
243
284
|
.from("ingestions")
|
|
244
285
|
.update({ tags: normalized })
|
|
245
286
|
.eq("id", req.params["id"] as string)
|
|
246
|
-
.eq("
|
|
287
|
+
.eq("workspace_id", req.workspaceId);
|
|
247
288
|
if (error) {
|
|
248
289
|
res.status(500).json({ success: false, error: error.message });
|
|
249
290
|
return;
|
|
@@ -260,7 +301,11 @@ router.delete(
|
|
|
260
301
|
res.status(401).json({ success: false, error: "Authentication required" });
|
|
261
302
|
return;
|
|
262
303
|
}
|
|
263
|
-
|
|
304
|
+
if (!req.workspaceId) {
|
|
305
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const deleted = await IngestionService.delete(req.params["id"] as string, req.supabase, req.workspaceId);
|
|
264
309
|
if (!deleted) {
|
|
265
310
|
res.status(404).json({ success: false, error: "Not found" });
|
|
266
311
|
return;
|
|
@@ -15,8 +15,17 @@ router.use(optionalAuth);
|
|
|
15
15
|
router.get(
|
|
16
16
|
"/",
|
|
17
17
|
asyncHandler(async (req, res) => {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
if (!req.supabase || !req.user) {
|
|
19
|
+
const policies = await PolicyLoader.load(false, req.supabase, req.workspaceId);
|
|
20
|
+
res.json({ success: true, policies });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!req.workspaceId) {
|
|
24
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const policies = await PolicyLoader.load(false, req.supabase, req.workspaceId);
|
|
28
|
+
if (policies.length === 0) {
|
|
20
29
|
res.json({ success: true, policies });
|
|
21
30
|
return;
|
|
22
31
|
}
|
|
@@ -24,6 +33,7 @@ router.get(
|
|
|
24
33
|
const stats = await PolicyLearningService.getPolicyLearningStats({
|
|
25
34
|
supabase: req.supabase,
|
|
26
35
|
userId: req.user.id,
|
|
36
|
+
workspaceId: req.workspaceId,
|
|
27
37
|
policyIds: policies.map((policy) => policy.metadata.id),
|
|
28
38
|
});
|
|
29
39
|
|
|
@@ -47,12 +57,20 @@ router.get(
|
|
|
47
57
|
router.post(
|
|
48
58
|
"/",
|
|
49
59
|
asyncHandler(async (req, res) => {
|
|
60
|
+
if (!req.supabase || !req.user) {
|
|
61
|
+
res.status(401).json({ success: false, error: "Authentication required" });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!req.workspaceId) {
|
|
65
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
50
68
|
const policy = req.body;
|
|
51
69
|
if (!PolicyLoader.validate(policy)) {
|
|
52
70
|
res.status(400).json({ success: false, error: "Invalid policy schema" });
|
|
53
71
|
return;
|
|
54
72
|
}
|
|
55
|
-
const filePath = await PolicyLoader.save(policy, req.supabase, req.user
|
|
73
|
+
const filePath = await PolicyLoader.save(policy, req.supabase, req.user.id, req.workspaceId);
|
|
56
74
|
res.status(201).json({ success: true, filePath });
|
|
57
75
|
})
|
|
58
76
|
);
|
|
@@ -61,7 +79,15 @@ router.post(
|
|
|
61
79
|
router.delete(
|
|
62
80
|
"/:id",
|
|
63
81
|
asyncHandler(async (req, res) => {
|
|
64
|
-
|
|
82
|
+
if (!req.supabase || !req.user) {
|
|
83
|
+
res.status(401).json({ success: false, error: "Authentication required" });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!req.workspaceId) {
|
|
87
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const deleted = await PolicyLoader.delete(req.params["id"] as string, req.supabase, req.user.id, req.workspaceId);
|
|
65
91
|
if (!deleted) {
|
|
66
92
|
res.status(404).json({ success: false, error: "Policy not found" });
|
|
67
93
|
return;
|
|
@@ -74,12 +100,21 @@ router.delete(
|
|
|
74
100
|
router.patch(
|
|
75
101
|
"/:id",
|
|
76
102
|
asyncHandler(async (req, res) => {
|
|
103
|
+
if (!req.supabase || !req.user) {
|
|
104
|
+
res.status(401).json({ success: false, error: "Authentication required" });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!req.workspaceId) {
|
|
108
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
77
111
|
const { enabled, name, description, tags, priority } = req.body;
|
|
78
112
|
await PolicyLoader.patch(
|
|
79
113
|
req.params["id"] as string,
|
|
80
114
|
{ enabled, name, description, tags, priority },
|
|
81
115
|
req.supabase,
|
|
82
|
-
req.user
|
|
116
|
+
req.user.id,
|
|
117
|
+
req.workspaceId
|
|
83
118
|
);
|
|
84
119
|
res.json({ success: true });
|
|
85
120
|
})
|
|
@@ -89,8 +124,16 @@ router.patch(
|
|
|
89
124
|
router.post(
|
|
90
125
|
"/reload",
|
|
91
126
|
asyncHandler(async (req, res) => {
|
|
92
|
-
|
|
93
|
-
|
|
127
|
+
if (!req.supabase || !req.user) {
|
|
128
|
+
res.status(401).json({ success: false, error: "Authentication required" });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!req.workspaceId) {
|
|
132
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
PolicyLoader.invalidateCache(req.workspaceId);
|
|
136
|
+
const policies = await PolicyLoader.load(true, req.supabase, req.workspaceId);
|
|
94
137
|
res.json({ success: true, count: policies.length });
|
|
95
138
|
})
|
|
96
139
|
);
|
package/api/src/routes/stats.ts
CHANGED
|
@@ -20,33 +20,37 @@ router.get("/", async (req, res) => {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
try {
|
|
23
|
-
const
|
|
23
|
+
const workspaceId = req.workspaceId;
|
|
24
|
+
if (!workspaceId) {
|
|
25
|
+
res.status(403).json({ success: false, error: "Workspace membership required" });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
24
28
|
const s = req.supabase; // the scoped service client
|
|
25
29
|
|
|
26
30
|
// 1. Total Documents Ingested
|
|
27
31
|
const { count: totalDocumentsCount, error: err1 } = await s
|
|
28
32
|
.from("ingestions")
|
|
29
33
|
.select("*", { count: 'exact', head: true })
|
|
30
|
-
.eq("
|
|
34
|
+
.eq("workspace_id", workspaceId);
|
|
31
35
|
|
|
32
36
|
// 2. Active Policies
|
|
33
37
|
const { count: activePoliciesCount, error: err2 } = await s
|
|
34
38
|
.from("policies")
|
|
35
39
|
.select("*", { count: 'exact', head: true })
|
|
36
|
-
.eq("
|
|
40
|
+
.eq("workspace_id", workspaceId)
|
|
37
41
|
.eq("enabled", true);
|
|
38
42
|
|
|
39
43
|
// 3. RAG Knowledge Base (Chunks)
|
|
40
44
|
const { count: ragChunksCount, error: err3 } = await s
|
|
41
45
|
.from("document_chunks")
|
|
42
46
|
.select("*", { count: 'exact', head: true })
|
|
43
|
-
.eq("
|
|
47
|
+
.eq("workspace_id", workspaceId);
|
|
44
48
|
|
|
45
49
|
// 4. Automation Runs (Sum of actions taken across ingestions)
|
|
46
50
|
const { data: ingestionsWithActions, error: err4 } = await s
|
|
47
51
|
.from("ingestions")
|
|
48
52
|
.select("actions_taken")
|
|
49
|
-
.eq("
|
|
53
|
+
.eq("workspace_id", workspaceId)
|
|
50
54
|
.not("actions_taken", "is", null);
|
|
51
55
|
|
|
52
56
|
let automationRunsCount = 0;
|