@vibes.diy/api-svc 14.1.21 → 14.1.22
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/create-handler.d.ts +2 -1
- package/create-handler.js +1 -0
- package/create-handler.js.map +1 -1
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/index.js.map +1 -1
- package/intern/agent-belt.js +15 -4
- package/intern/agent-belt.js.map +1 -1
- package/intern/agent-build-tools.js +5 -2
- package/intern/agent-build-tools.js.map +1 -1
- package/intern/agent-debug-tools.d.ts +33 -0
- package/intern/agent-debug-tools.js +676 -0
- package/intern/agent-debug-tools.js.map +1 -0
- package/intern/agent-file-tools.js +8 -2
- package/intern/agent-file-tools.js.map +1 -1
- package/intern/agent-instruction.d.ts +1 -1
- package/intern/agent-instruction.js +2 -2
- package/intern/agent-instruction.js.map +1 -1
- package/intern/agent-session-instruction.d.ts +2 -2
- package/intern/agent-session-instruction.js +15 -2
- package/intern/agent-session-instruction.js.map +1 -1
- package/intern/agent-session-runtime.d.ts +1 -0
- package/intern/agent-session-runtime.js +8 -3
- package/intern/agent-session-runtime.js.map +1 -1
- package/intern/attempt-backend-fetch.js +7 -1
- package/intern/attempt-backend-fetch.js.map +1 -1
- package/intern/backend-do-addr.d.ts +2 -0
- package/intern/backend-do-addr.js +2 -0
- package/intern/backend-do-addr.js.map +1 -1
- package/intern/backend-do-control-plane.d.ts +18 -0
- package/intern/backend-do-control-plane.js +90 -1
- package/intern/backend-do-control-plane.js.map +1 -1
- package/intern/mint-decision.d.ts +8 -0
- package/intern/mint-decision.js +15 -3
- package/intern/mint-decision.js.map +1 -1
- package/intern/resolve-backend-session-user.d.ts +8 -0
- package/intern/resolve-backend-session-user.js +10 -0
- package/intern/resolve-backend-session-user.js.map +1 -1
- package/package.json +11 -11
- package/public/app-documents-broadcast.d.ts +2 -0
- package/public/app-documents-broadcast.js +7 -0
- package/public/app-documents-broadcast.js.map +1 -1
- package/public/app-documents-read-eventos.d.ts +52 -1
- package/public/app-documents-read-eventos.js +250 -222
- package/public/app-documents-read-eventos.js.map +1 -1
- package/public/app-documents-write-eventos.d.ts +42 -0
- package/public/app-documents-write-eventos.js +334 -334
- package/public/app-documents-write-eventos.js.map +1 -1
- package/public/app-logs.d.ts +14 -2
- package/public/app-logs.js +60 -45
- package/public/app-logs.js.map +1 -1
- package/public/app-setting-items.d.ts +4 -0
- package/public/app-setting-items.js +6 -3
- package/public/app-setting-items.js.map +1 -1
- package/public/app-settings-sql-upsert.d.ts +4 -0
- package/public/app-settings-sql-upsert.js +1 -1
- package/public/app-settings-sql-upsert.js.map +1 -1
- package/public/prompt-chat-section.js +1 -0
- package/public/prompt-chat-section.js.map +1 -1
- package/types.d.ts +2 -1
- package/types.js.map +1 -1
|
@@ -14,6 +14,7 @@ import { filterDocsByChannel } from "./channel-read-filter.js";
|
|
|
14
14
|
import { mintFilesUrls } from "./files-url-mint.js";
|
|
15
15
|
import { applyQueryFilter } from "./app-documents-query-filter.js";
|
|
16
16
|
import { readAllowed, clientWsSend, connectionAdminMode } from "./app-documents-shared.js";
|
|
17
|
+
import { addressesDirectChannel } from "./dm-lane.js";
|
|
17
18
|
import { resolveActingHandle, resolveActiveHandle } from "./resolve-active-handle.js";
|
|
18
19
|
import { audienceChannelsOf, audienceChannelsForReader, collectAudienceSubjects, inheritChannelOf, INHERIT_CAPABILITY_CHANNEL, RESERVED_CHANNEL_PREFIX, } from "./audience-channels.js";
|
|
19
20
|
import { resolveInheritReadChannels } from "./inherit-read-resolver.js";
|
|
@@ -64,6 +65,128 @@ async function validateReadActingPin(vctx, userId, pin) {
|
|
|
64
65
|
return { ok: false };
|
|
65
66
|
return { ok: true, actingHandle: pin };
|
|
66
67
|
}
|
|
68
|
+
export async function getDocForCaller(vctx, args) {
|
|
69
|
+
if (addressesDirectChannel(args.ownerHandle))
|
|
70
|
+
return { kind: "denied", message: "Access denied" };
|
|
71
|
+
const authenticated = args.userId !== null;
|
|
72
|
+
const getPin = await validateReadActingPin(vctx, args.userId, args.actingHandle);
|
|
73
|
+
if (!getPin.ok)
|
|
74
|
+
return { kind: "denied", message: "Acting handle not bound" };
|
|
75
|
+
const { access, blocked } = args.userId !== null
|
|
76
|
+
? await checkDocAccess(vctx, args.userId, args.appSlug, args.ownerHandle, args.adminMode, "get-doc")
|
|
77
|
+
: { access: "none", blocked: false };
|
|
78
|
+
const rEnv = await resolveDbAccess({
|
|
79
|
+
vctx,
|
|
80
|
+
ownerHandle: args.ownerHandle,
|
|
81
|
+
appSlug: args.appSlug,
|
|
82
|
+
dbName: args.dbName,
|
|
83
|
+
});
|
|
84
|
+
if (rEnv.isErr() ||
|
|
85
|
+
!(await readAllowed({
|
|
86
|
+
vctx,
|
|
87
|
+
env: rEnv.Ok(),
|
|
88
|
+
caller: { access, authenticated, blocked },
|
|
89
|
+
appSlug: args.appSlug,
|
|
90
|
+
ownerHandle: args.ownerHandle,
|
|
91
|
+
}))) {
|
|
92
|
+
return { kind: "denied", message: "Access denied" };
|
|
93
|
+
}
|
|
94
|
+
const t = vctx.sql.tables.appDocuments;
|
|
95
|
+
const row = await vctx.sql.db
|
|
96
|
+
.select()
|
|
97
|
+
.from(t)
|
|
98
|
+
.where(and(eq(t.ownerHandle, args.ownerHandle), eq(t.appSlug, args.appSlug), eq(t.dbName, args.dbName), eq(t.docId, args.docId)))
|
|
99
|
+
.orderBy(sql `${t.seq} desc`)
|
|
100
|
+
.limit(1)
|
|
101
|
+
.then((r) => r[0]);
|
|
102
|
+
if (!row || row.deleted === 1)
|
|
103
|
+
return { kind: "not-found", docId: args.docId };
|
|
104
|
+
const afbRowG = await resolveAccessBinding(vctx, args.ownerHandle, args.appSlug, args.dbName);
|
|
105
|
+
if (afbRowG?.accessFnCid && access !== "override") {
|
|
106
|
+
const tOutputsG = vctx.sql.tables.accessFnOutputs;
|
|
107
|
+
const docOutput = await vctx.sql.db
|
|
108
|
+
.select({ docId: tOutputsG.docId, output: tOutputsG.output })
|
|
109
|
+
.from(tOutputsG)
|
|
110
|
+
.where(and(eq(tOutputsG.ownerHandle, args.ownerHandle), eq(tOutputsG.appSlug, args.appSlug), eq(tOutputsG.dbName, args.dbName), eq(tOutputsG.docId, args.docId)))
|
|
111
|
+
.limit(1)
|
|
112
|
+
.then((r) => r[0]);
|
|
113
|
+
const decodedDocOutput = docOutput === undefined ? undefined : decodeAccessOutputRow(docOutput);
|
|
114
|
+
const parsed = decodedDocOutput?.descriptor;
|
|
115
|
+
const docChannels = parsed?.channels;
|
|
116
|
+
const docAudienceChannels = parsed === undefined ? [] : audienceChannelsOf(parsed);
|
|
117
|
+
const docInheritChannels = parsed === undefined ? [] : inheritChannelOf(parsed);
|
|
118
|
+
if ((docChannels === undefined || docChannels.length === 0) &&
|
|
119
|
+
docAudienceChannels.length === 0 &&
|
|
120
|
+
docInheritChannels.length === 0) {
|
|
121
|
+
return { kind: "not-found", docId: args.docId };
|
|
122
|
+
}
|
|
123
|
+
const grantOutputs = await vctx.sql.db
|
|
124
|
+
.select({ docId: tOutputsG.docId, output: tOutputsG.output })
|
|
125
|
+
.from(tOutputsG)
|
|
126
|
+
.where(and(eq(tOutputsG.ownerHandle, args.ownerHandle), eq(tOutputsG.appSlug, args.appSlug), eq(tOutputsG.dbName, args.dbName), eq(tOutputsG.hasGrants, 1)));
|
|
127
|
+
const grantRowsExcludingDoc = docOutput === undefined ? grantOutputs : grantOutputs.filter((r) => r.docId !== docOutput.docId);
|
|
128
|
+
const decodedGrantOutputs = decodeAccessOutputRows(grantRowsExcludingDoc);
|
|
129
|
+
const devHandles = authenticated
|
|
130
|
+
? await resolveDeveloperHandles(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug })
|
|
131
|
+
: [];
|
|
132
|
+
const reduce = newSeededReduce(args.ownerHandle, devHandles);
|
|
133
|
+
if (decodedDocOutput !== undefined && grantOutputs.some((grantOutput) => grantOutput.docId === args.docId)) {
|
|
134
|
+
reduce.addDoc(args.docId, extractContribution(decodedDocOutput.descriptor));
|
|
135
|
+
}
|
|
136
|
+
for (const { row: grantRow, descriptor } of decodedGrantOutputs) {
|
|
137
|
+
reduce.addDoc(grantRow.docId, extractContribution(descriptor));
|
|
138
|
+
}
|
|
139
|
+
const userHandle = args.userId !== null ? (getPin.actingHandle ?? (await resolveActiveHandle(vctx, args.userId)) ?? null) : null;
|
|
140
|
+
const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
|
|
141
|
+
if (parsed !== undefined && docAudienceChannels.length > 0) {
|
|
142
|
+
const subjects = collectAudienceSubjects([parsed]);
|
|
143
|
+
if (userHandle !== null) {
|
|
144
|
+
const membership = await resolveAudienceMembershipFailClosed(vctx, {
|
|
145
|
+
handle: userHandle,
|
|
146
|
+
subjects,
|
|
147
|
+
ownerHandle: args.ownerHandle,
|
|
148
|
+
appSlug: args.appSlug,
|
|
149
|
+
});
|
|
150
|
+
for (const ch of audienceChannelsForReader(membership))
|
|
151
|
+
effectiveChannels.add(ch);
|
|
152
|
+
}
|
|
153
|
+
const publicChannels = await resolvePublicAudienceChannelsFailClosed(vctx, {
|
|
154
|
+
handle: userHandle,
|
|
155
|
+
subjects,
|
|
156
|
+
ownerHandle: args.ownerHandle,
|
|
157
|
+
appSlug: args.appSlug,
|
|
158
|
+
});
|
|
159
|
+
for (const ch of publicChannels)
|
|
160
|
+
effectiveChannels.add(ch);
|
|
161
|
+
}
|
|
162
|
+
if (docInheritChannels.length > 0 && decodedDocOutput !== undefined) {
|
|
163
|
+
const inheritCh = await resolveInheritReadChannels(vctx, {
|
|
164
|
+
ownerHandle: args.ownerHandle,
|
|
165
|
+
appSlug: args.appSlug,
|
|
166
|
+
outputs: [decodedDocOutput],
|
|
167
|
+
userHandle,
|
|
168
|
+
authenticated,
|
|
169
|
+
blocked,
|
|
170
|
+
access,
|
|
171
|
+
devHandles,
|
|
172
|
+
});
|
|
173
|
+
for (const ch of inheritCh)
|
|
174
|
+
effectiveChannels.add(ch);
|
|
175
|
+
}
|
|
176
|
+
const docChannelsWithAudience = [...(docChannels ?? []), ...docAudienceChannels, ...docInheritChannels];
|
|
177
|
+
const hasAccess = docChannelsWithAudience.some((ch) => effectiveChannels.has(ch) || reduce.publicChannels.has(ch));
|
|
178
|
+
if (!hasAccess)
|
|
179
|
+
return { kind: "not-found", docId: args.docId };
|
|
180
|
+
}
|
|
181
|
+
const doc = mintFilesUrls(row.data, {
|
|
182
|
+
ownerHandle: args.ownerHandle,
|
|
183
|
+
appSlug: args.appSlug,
|
|
184
|
+
dbName: args.dbName,
|
|
185
|
+
docId: row.docId,
|
|
186
|
+
svc: vctx.params.vibes.svc,
|
|
187
|
+
});
|
|
188
|
+
return { kind: "ok", docId: row.docId, doc };
|
|
189
|
+
}
|
|
67
190
|
export const getDocEvento = {
|
|
68
191
|
hash: "get-doc",
|
|
69
192
|
validate: unwrapMsgBase(async (msg) => {
|
|
@@ -76,160 +199,122 @@ export const getDocEvento = {
|
|
|
76
199
|
handle: optAuth(async (ctx) => {
|
|
77
200
|
const req = ctx.validated.payload;
|
|
78
201
|
const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
await ctx.send.send(ctx, {
|
|
82
|
-
type: "vibes.diy.res-error",
|
|
83
|
-
error: { message: "Acting handle not bound" },
|
|
84
|
-
});
|
|
85
|
-
return Result.Ok(EventoResult.Continue);
|
|
86
|
-
}
|
|
87
|
-
const reqAdmin = req.adminMode === true;
|
|
88
|
-
const { access, blocked } = req._auth
|
|
89
|
-
? await checkDocAccess(vctx, req._auth.verifiedAuth.claims.userId, req.appSlug, req.ownerHandle, connectionAdminMode(ctx) || reqAdmin, "get-doc")
|
|
90
|
-
: { access: "none", blocked: false };
|
|
91
|
-
const rEnv = await resolveDbAccess({
|
|
92
|
-
vctx,
|
|
202
|
+
const answer = await getDocForCaller(vctx, {
|
|
203
|
+
userId: req._auth?.verifiedAuth.claims.userId ?? null,
|
|
93
204
|
ownerHandle: req.ownerHandle,
|
|
94
205
|
appSlug: req.appSlug,
|
|
95
206
|
dbName: req.dbName,
|
|
207
|
+
docId: req.docId,
|
|
208
|
+
adminMode: connectionAdminMode(ctx) || req.adminMode === true,
|
|
209
|
+
...(req.actingHandle === undefined ? {} : { actingHandle: req.actingHandle }),
|
|
96
210
|
});
|
|
97
|
-
if (
|
|
98
|
-
!(await readAllowed({
|
|
99
|
-
vctx,
|
|
100
|
-
env: rEnv.Ok(),
|
|
101
|
-
caller: { access, authenticated: req._auth !== undefined, blocked },
|
|
102
|
-
appSlug: req.appSlug,
|
|
103
|
-
ownerHandle: req.ownerHandle,
|
|
104
|
-
}))) {
|
|
211
|
+
if (answer.kind === "denied") {
|
|
105
212
|
await ctx.send.send(ctx, {
|
|
106
213
|
type: "vibes.diy.res-error",
|
|
107
|
-
error: { message:
|
|
214
|
+
error: { message: answer.message },
|
|
108
215
|
});
|
|
109
216
|
return Result.Ok(EventoResult.Continue);
|
|
110
217
|
}
|
|
111
|
-
|
|
112
|
-
const row = await vctx.sql.db
|
|
113
|
-
.select()
|
|
114
|
-
.from(t)
|
|
115
|
-
.where(and(eq(t.ownerHandle, req.ownerHandle), eq(t.appSlug, req.appSlug), eq(t.dbName, req.dbName), eq(t.docId, req.docId)))
|
|
116
|
-
.orderBy(sql `${t.seq} desc`)
|
|
117
|
-
.limit(1)
|
|
118
|
-
.then((r) => r[0]);
|
|
119
|
-
if (!row || row.deleted === 1) {
|
|
218
|
+
if (answer.kind === "not-found") {
|
|
120
219
|
await ctx.send.send(ctx, {
|
|
121
220
|
type: "vibes.diy.res-get-doc",
|
|
122
221
|
status: "not-found",
|
|
123
|
-
id:
|
|
222
|
+
id: answer.docId,
|
|
124
223
|
});
|
|
125
224
|
return Result.Ok(EventoResult.Continue);
|
|
126
225
|
}
|
|
127
|
-
const afbRowG = await resolveAccessBinding(vctx, req.ownerHandle, req.appSlug, req.dbName);
|
|
128
|
-
if (afbRowG?.accessFnCid && access !== "override") {
|
|
129
|
-
const tOutputsG = vctx.sql.tables.accessFnOutputs;
|
|
130
|
-
const docOutput = await vctx.sql.db
|
|
131
|
-
.select({ docId: tOutputsG.docId, output: tOutputsG.output })
|
|
132
|
-
.from(tOutputsG)
|
|
133
|
-
.where(and(eq(tOutputsG.ownerHandle, req.ownerHandle), eq(tOutputsG.appSlug, req.appSlug), eq(tOutputsG.dbName, req.dbName), eq(tOutputsG.docId, req.docId)))
|
|
134
|
-
.limit(1)
|
|
135
|
-
.then((r) => r[0]);
|
|
136
|
-
const decodedDocOutput = docOutput === undefined ? undefined : decodeAccessOutputRow(docOutput);
|
|
137
|
-
const parsed = decodedDocOutput?.descriptor;
|
|
138
|
-
const docChannels = parsed?.channels;
|
|
139
|
-
const docAudienceChannels = parsed === undefined ? [] : audienceChannelsOf(parsed);
|
|
140
|
-
const docInheritChannels = parsed === undefined ? [] : inheritChannelOf(parsed);
|
|
141
|
-
if ((docChannels === undefined || docChannels.length === 0) &&
|
|
142
|
-
docAudienceChannels.length === 0 &&
|
|
143
|
-
docInheritChannels.length === 0) {
|
|
144
|
-
await ctx.send.send(ctx, {
|
|
145
|
-
type: "vibes.diy.res-get-doc",
|
|
146
|
-
status: "not-found",
|
|
147
|
-
id: req.docId,
|
|
148
|
-
});
|
|
149
|
-
return Result.Ok(EventoResult.Continue);
|
|
150
|
-
}
|
|
151
|
-
const grantOutputs = await vctx.sql.db
|
|
152
|
-
.select({ docId: tOutputsG.docId, output: tOutputsG.output })
|
|
153
|
-
.from(tOutputsG)
|
|
154
|
-
.where(and(eq(tOutputsG.ownerHandle, req.ownerHandle), eq(tOutputsG.appSlug, req.appSlug), eq(tOutputsG.dbName, req.dbName), eq(tOutputsG.hasGrants, 1)));
|
|
155
|
-
const grantRowsExcludingDoc = docOutput === undefined ? grantOutputs : grantOutputs.filter((r) => r.docId !== docOutput.docId);
|
|
156
|
-
const decodedGrantOutputs = decodeAccessOutputRows(grantRowsExcludingDoc);
|
|
157
|
-
const devHandles = req._auth !== undefined
|
|
158
|
-
? await resolveDeveloperHandles(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug })
|
|
159
|
-
: [];
|
|
160
|
-
const reduce = newSeededReduce(req.ownerHandle, devHandles);
|
|
161
|
-
if (decodedDocOutput !== undefined && grantOutputs.some((grantOutput) => grantOutput.docId === req.docId)) {
|
|
162
|
-
reduce.addDoc(req.docId, extractContribution(decodedDocOutput.descriptor));
|
|
163
|
-
}
|
|
164
|
-
for (const { row, descriptor } of decodedGrantOutputs) {
|
|
165
|
-
reduce.addDoc(row.docId, extractContribution(descriptor));
|
|
166
|
-
}
|
|
167
|
-
const userHandle = req._auth
|
|
168
|
-
? (getPin.actingHandle ?? (await resolveActiveHandle(vctx, req._auth.verifiedAuth.claims.userId)) ?? null)
|
|
169
|
-
: null;
|
|
170
|
-
const effectiveChannels = userHandle !== null ? reduce.resolveEffectiveChannels(userHandle) : new Set();
|
|
171
|
-
if (parsed !== undefined && docAudienceChannels.length > 0) {
|
|
172
|
-
const subjects = collectAudienceSubjects([parsed]);
|
|
173
|
-
if (userHandle !== null) {
|
|
174
|
-
const membership = await resolveAudienceMembershipFailClosed(vctx, {
|
|
175
|
-
handle: userHandle,
|
|
176
|
-
subjects,
|
|
177
|
-
ownerHandle: req.ownerHandle,
|
|
178
|
-
appSlug: req.appSlug,
|
|
179
|
-
});
|
|
180
|
-
for (const ch of audienceChannelsForReader(membership))
|
|
181
|
-
effectiveChannels.add(ch);
|
|
182
|
-
}
|
|
183
|
-
const publicChannels = await resolvePublicAudienceChannelsFailClosed(vctx, {
|
|
184
|
-
handle: userHandle,
|
|
185
|
-
subjects,
|
|
186
|
-
ownerHandle: req.ownerHandle,
|
|
187
|
-
appSlug: req.appSlug,
|
|
188
|
-
});
|
|
189
|
-
for (const ch of publicChannels)
|
|
190
|
-
effectiveChannels.add(ch);
|
|
191
|
-
}
|
|
192
|
-
if (docInheritChannels.length > 0 && decodedDocOutput !== undefined) {
|
|
193
|
-
const inheritCh = await resolveInheritReadChannels(vctx, {
|
|
194
|
-
ownerHandle: req.ownerHandle,
|
|
195
|
-
appSlug: req.appSlug,
|
|
196
|
-
outputs: [decodedDocOutput],
|
|
197
|
-
userHandle,
|
|
198
|
-
authenticated: req._auth !== undefined,
|
|
199
|
-
blocked,
|
|
200
|
-
access,
|
|
201
|
-
devHandles,
|
|
202
|
-
});
|
|
203
|
-
for (const ch of inheritCh)
|
|
204
|
-
effectiveChannels.add(ch);
|
|
205
|
-
}
|
|
206
|
-
const docChannelsWithAudience = [...(docChannels ?? []), ...docAudienceChannels, ...docInheritChannels];
|
|
207
|
-
const hasAccess = docChannelsWithAudience.some((ch) => effectiveChannels.has(ch) || reduce.publicChannels.has(ch));
|
|
208
|
-
if (!hasAccess) {
|
|
209
|
-
await ctx.send.send(ctx, {
|
|
210
|
-
type: "vibes.diy.res-get-doc",
|
|
211
|
-
status: "not-found",
|
|
212
|
-
id: req.docId,
|
|
213
|
-
});
|
|
214
|
-
return Result.Ok(EventoResult.Continue);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
const doc = mintFilesUrls(row.data, {
|
|
218
|
-
ownerHandle: req.ownerHandle,
|
|
219
|
-
appSlug: req.appSlug,
|
|
220
|
-
dbName: req.dbName,
|
|
221
|
-
docId: row.docId,
|
|
222
|
-
svc: vctx.params.vibes.svc,
|
|
223
|
-
});
|
|
224
226
|
await ctx.send.send(ctx, {
|
|
225
227
|
type: "vibes.diy.res-get-doc",
|
|
226
228
|
status: "ok",
|
|
227
|
-
id:
|
|
228
|
-
doc,
|
|
229
|
+
id: answer.docId,
|
|
230
|
+
doc: answer.doc,
|
|
229
231
|
});
|
|
230
232
|
return Result.Ok(EventoResult.Continue);
|
|
231
233
|
}),
|
|
232
234
|
};
|
|
235
|
+
export async function queryDocsForCaller(vctx, args) {
|
|
236
|
+
if (addressesDirectChannel(args.ownerHandle))
|
|
237
|
+
return { kind: "denied", message: "Access denied" };
|
|
238
|
+
const authenticated = args.userId !== null;
|
|
239
|
+
const queryPin = await validateReadActingPin(vctx, args.userId, args.actingHandle);
|
|
240
|
+
if (!queryPin.ok)
|
|
241
|
+
return { kind: "denied", message: "Acting handle not bound" };
|
|
242
|
+
const afbRowQ = await resolveAccessBinding(vctx, args.ownerHandle, args.appSlug, args.dbName);
|
|
243
|
+
const { access, blocked = false } = args.userId !== null
|
|
244
|
+
? await checkDocAccess(vctx, args.userId, args.appSlug, args.ownerHandle, args.adminMode, "query-docs")
|
|
245
|
+
: { access: "none", blocked: false };
|
|
246
|
+
const rEnv = await resolveDbAccess({
|
|
247
|
+
vctx,
|
|
248
|
+
ownerHandle: args.ownerHandle,
|
|
249
|
+
appSlug: args.appSlug,
|
|
250
|
+
dbName: args.dbName,
|
|
251
|
+
});
|
|
252
|
+
if (rEnv.isErr() ||
|
|
253
|
+
!(await readAllowed({
|
|
254
|
+
vctx,
|
|
255
|
+
env: rEnv.Ok(),
|
|
256
|
+
caller: { access, authenticated, blocked },
|
|
257
|
+
appSlug: args.appSlug,
|
|
258
|
+
ownerHandle: args.ownerHandle,
|
|
259
|
+
}))) {
|
|
260
|
+
return { kind: "denied", message: "Access denied" };
|
|
261
|
+
}
|
|
262
|
+
const pageReq = resolveQueryPage(args);
|
|
263
|
+
const rows = pageReq.paged
|
|
264
|
+
? await loadHeadDocsPage(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName: args.dbName }, { limit: pageReq.limit, ...(pageReq.after !== undefined ? { after: pageReq.after } : {}) })
|
|
265
|
+
: await loadHeadDocs(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName: args.dbName });
|
|
266
|
+
const next = pageReq.paged && rows.length === pageReq.limit ? rows[rows.length - 1].docId : undefined;
|
|
267
|
+
const docs = [];
|
|
268
|
+
for (const row of rows) {
|
|
269
|
+
const doc = mintFilesUrls(row.data, {
|
|
270
|
+
ownerHandle: args.ownerHandle,
|
|
271
|
+
appSlug: args.appSlug,
|
|
272
|
+
dbName: args.dbName,
|
|
273
|
+
docId: row.docId,
|
|
274
|
+
svc: vctx.params.vibes.svc,
|
|
275
|
+
});
|
|
276
|
+
docs.push({
|
|
277
|
+
_id: row.docId,
|
|
278
|
+
...doc,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
let channelFilteredDocs = docs;
|
|
282
|
+
if (afbRowQ?.accessFnCid) {
|
|
283
|
+
const paged = pageReq.paged
|
|
284
|
+
? await readPagedOutputInputs(vctx, { ownerHandle: args.ownerHandle, appSlug: args.appSlug, dbName: args.dbName }, { pageDocIds: rows.map((r) => r.docId), wantDigest: access !== "override" })
|
|
285
|
+
: undefined;
|
|
286
|
+
const tOutputsQ = vctx.sql.tables.accessFnOutputs;
|
|
287
|
+
const decodedOutputs = paged?.decodedOutputs ??
|
|
288
|
+
decodeAccessOutputRows(await vctx.sql.db
|
|
289
|
+
.select({ docId: tOutputsQ.docId, output: tOutputsQ.output })
|
|
290
|
+
.from(tOutputsQ)
|
|
291
|
+
.where(and(eq(tOutputsQ.ownerHandle, args.ownerHandle), eq(tOutputsQ.appSlug, args.appSlug), eq(tOutputsQ.dbName, args.dbName))));
|
|
292
|
+
if (paged === undefined)
|
|
293
|
+
await runQueryReadInterleaveHookForTests();
|
|
294
|
+
const { userHandle, effectiveChannels, publicChannels } = await resolveViewerChannels(vctx, {
|
|
295
|
+
ownerHandle: args.ownerHandle,
|
|
296
|
+
appSlug: args.appSlug,
|
|
297
|
+
dbName: args.dbName,
|
|
298
|
+
userId: args.userId,
|
|
299
|
+
blocked,
|
|
300
|
+
access,
|
|
301
|
+
decodedOutputs,
|
|
302
|
+
derivation: "readable",
|
|
303
|
+
...(queryPin.actingHandle !== undefined ? { actingHandle: queryPin.actingHandle } : {}),
|
|
304
|
+
...(paged !== undefined ? { boundedInputs: paged.boundedInputs } : {}),
|
|
305
|
+
});
|
|
306
|
+
channelFilteredDocs = filterDocsByChannel({
|
|
307
|
+
docs,
|
|
308
|
+
outputRows: decodedOutputs,
|
|
309
|
+
userHandle,
|
|
310
|
+
effectiveChannels,
|
|
311
|
+
publicChannels,
|
|
312
|
+
adminOverride: access === "override",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const filteredDocs = applyQueryFilter(channelFilteredDocs, args.filter);
|
|
316
|
+
return { kind: "ok", docs: filteredDocs, ...(next !== undefined ? { next } : {}) };
|
|
317
|
+
}
|
|
233
318
|
export const queryDocsEvento = {
|
|
234
319
|
hash: "query-docs",
|
|
235
320
|
validate: unwrapMsgBase(async (msg) => {
|
|
@@ -242,98 +327,29 @@ export const queryDocsEvento = {
|
|
|
242
327
|
handle: optAuth(async (ctx) => {
|
|
243
328
|
const req = ctx.validated.payload;
|
|
244
329
|
const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
await ctx.send.send(ctx, {
|
|
248
|
-
type: "vibes.diy.res-error",
|
|
249
|
-
error: { message: "Acting handle not bound" },
|
|
250
|
-
});
|
|
251
|
-
return Result.Ok(EventoResult.Continue);
|
|
252
|
-
}
|
|
253
|
-
const afbRowQ = await resolveAccessBinding(vctx, req.ownerHandle, req.appSlug, req.dbName);
|
|
254
|
-
const reqAdmin = req.adminMode === true;
|
|
255
|
-
const { access, blocked = false } = req._auth
|
|
256
|
-
? await checkDocAccess(vctx, req._auth.verifiedAuth.claims.userId, req.appSlug, req.ownerHandle, connectionAdminMode(ctx) || reqAdmin, "query-docs")
|
|
257
|
-
: { access: "none", blocked: false };
|
|
258
|
-
const rEnv = await resolveDbAccess({
|
|
259
|
-
vctx,
|
|
330
|
+
const answer = await queryDocsForCaller(vctx, {
|
|
331
|
+
userId: req._auth?.verifiedAuth.claims.userId ?? null,
|
|
260
332
|
ownerHandle: req.ownerHandle,
|
|
261
333
|
appSlug: req.appSlug,
|
|
262
334
|
dbName: req.dbName,
|
|
335
|
+
adminMode: connectionAdminMode(ctx) || req.adminMode === true,
|
|
336
|
+
...(req.actingHandle === undefined ? {} : { actingHandle: req.actingHandle }),
|
|
337
|
+
...(req.filter === undefined ? {} : { filter: req.filter }),
|
|
338
|
+
...(req.limit === undefined ? {} : { limit: req.limit }),
|
|
339
|
+
...(req.after === undefined ? {} : { after: req.after }),
|
|
263
340
|
});
|
|
264
|
-
if (
|
|
265
|
-
!(await readAllowed({
|
|
266
|
-
vctx,
|
|
267
|
-
env: rEnv.Ok(),
|
|
268
|
-
caller: { access, authenticated: req._auth !== undefined, blocked },
|
|
269
|
-
appSlug: req.appSlug,
|
|
270
|
-
ownerHandle: req.ownerHandle,
|
|
271
|
-
}))) {
|
|
341
|
+
if (answer.kind === "denied") {
|
|
272
342
|
await ctx.send.send(ctx, {
|
|
273
343
|
type: "vibes.diy.res-error",
|
|
274
|
-
error: { message:
|
|
344
|
+
error: { message: answer.message },
|
|
275
345
|
});
|
|
276
346
|
return Result.Ok(EventoResult.Continue);
|
|
277
347
|
}
|
|
278
|
-
const pageReq = resolveQueryPage(req);
|
|
279
|
-
const rows = pageReq.paged
|
|
280
|
-
? await loadHeadDocsPage(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: req.dbName }, { limit: pageReq.limit, ...(pageReq.after !== undefined ? { after: pageReq.after } : {}) })
|
|
281
|
-
: await loadHeadDocs(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: req.dbName });
|
|
282
|
-
const next = pageReq.paged && rows.length === pageReq.limit ? rows[rows.length - 1].docId : undefined;
|
|
283
|
-
const docs = [];
|
|
284
|
-
for (const row of rows) {
|
|
285
|
-
const doc = mintFilesUrls(row.data, {
|
|
286
|
-
ownerHandle: req.ownerHandle,
|
|
287
|
-
appSlug: req.appSlug,
|
|
288
|
-
dbName: req.dbName,
|
|
289
|
-
docId: row.docId,
|
|
290
|
-
svc: vctx.params.vibes.svc,
|
|
291
|
-
});
|
|
292
|
-
docs.push({
|
|
293
|
-
_id: row.docId,
|
|
294
|
-
...doc,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
let channelFilteredDocs = docs;
|
|
298
|
-
if (afbRowQ?.accessFnCid) {
|
|
299
|
-
const paged = pageReq.paged
|
|
300
|
-
? await readPagedOutputInputs(vctx, { ownerHandle: req.ownerHandle, appSlug: req.appSlug, dbName: req.dbName }, { pageDocIds: rows.map((r) => r.docId), wantDigest: access !== "override" })
|
|
301
|
-
: undefined;
|
|
302
|
-
const tOutputsQ = vctx.sql.tables.accessFnOutputs;
|
|
303
|
-
const decodedOutputs = paged?.decodedOutputs ??
|
|
304
|
-
decodeAccessOutputRows(await vctx.sql.db
|
|
305
|
-
.select({ docId: tOutputsQ.docId, output: tOutputsQ.output })
|
|
306
|
-
.from(tOutputsQ)
|
|
307
|
-
.where(and(eq(tOutputsQ.ownerHandle, req.ownerHandle), eq(tOutputsQ.appSlug, req.appSlug), eq(tOutputsQ.dbName, req.dbName))));
|
|
308
|
-
if (paged === undefined)
|
|
309
|
-
await runQueryReadInterleaveHookForTests();
|
|
310
|
-
const { userHandle, effectiveChannels, publicChannels } = await resolveViewerChannels(vctx, {
|
|
311
|
-
ownerHandle: req.ownerHandle,
|
|
312
|
-
appSlug: req.appSlug,
|
|
313
|
-
dbName: req.dbName,
|
|
314
|
-
userId: req._auth?.verifiedAuth.claims.userId ?? null,
|
|
315
|
-
blocked,
|
|
316
|
-
access,
|
|
317
|
-
decodedOutputs,
|
|
318
|
-
derivation: "readable",
|
|
319
|
-
...(queryPin.actingHandle !== undefined ? { actingHandle: queryPin.actingHandle } : {}),
|
|
320
|
-
...(paged !== undefined ? { boundedInputs: paged.boundedInputs } : {}),
|
|
321
|
-
});
|
|
322
|
-
channelFilteredDocs = filterDocsByChannel({
|
|
323
|
-
docs,
|
|
324
|
-
outputRows: decodedOutputs,
|
|
325
|
-
userHandle,
|
|
326
|
-
effectiveChannels,
|
|
327
|
-
publicChannels,
|
|
328
|
-
adminOverride: access === "override",
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
const filteredDocs = applyQueryFilter(channelFilteredDocs, req.filter);
|
|
332
348
|
await ctx.send.send(ctx, {
|
|
333
349
|
type: "vibes.diy.res-query-docs",
|
|
334
350
|
status: "ok",
|
|
335
|
-
docs:
|
|
336
|
-
...(next !== undefined ? { next } : {}),
|
|
351
|
+
docs: [...answer.docs],
|
|
352
|
+
...(answer.next !== undefined ? { next: answer.next } : {}),
|
|
337
353
|
});
|
|
338
354
|
return Result.Ok(EventoResult.Continue);
|
|
339
355
|
}),
|
|
@@ -568,6 +584,19 @@ export const subscribeViewerGrantsEvento = {
|
|
|
568
584
|
return Result.Ok(EventoResult.Continue);
|
|
569
585
|
}),
|
|
570
586
|
};
|
|
587
|
+
export async function listDbNamesForCaller(vctx, args) {
|
|
588
|
+
if (addressesDirectChannel(args.ownerHandle))
|
|
589
|
+
return { kind: "denied", message: "Access denied" };
|
|
590
|
+
const { access, isOwner } = await checkDocAccess(vctx, args.userId, args.appSlug, args.ownerHandle, args.adminMode, "list-db-names");
|
|
591
|
+
if (isOwner === false && access !== "override")
|
|
592
|
+
return { kind: "denied", message: "Access denied" };
|
|
593
|
+
const t = vctx.sql.tables.appDocuments;
|
|
594
|
+
const rows = await vctx.sql.db
|
|
595
|
+
.selectDistinct({ dbName: t.dbName })
|
|
596
|
+
.from(t)
|
|
597
|
+
.where(and(eq(t.ownerHandle, args.ownerHandle), eq(t.appSlug, args.appSlug)));
|
|
598
|
+
return { kind: "ok", dbNames: rows.map((r) => r.dbName) };
|
|
599
|
+
}
|
|
571
600
|
export const listDbNamesEvento = {
|
|
572
601
|
hash: "list-db-names",
|
|
573
602
|
validate: unwrapMsgBase(async (msg) => {
|
|
@@ -580,21 +609,20 @@ export const listDbNamesEvento = {
|
|
|
580
609
|
handle: checkAuth(async (ctx) => {
|
|
581
610
|
const req = ctx.validated.payload;
|
|
582
611
|
const vctx = ctx.ctx.getOrThrow("vibesApiCtx");
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
612
|
+
const answer = await listDbNamesForCaller(vctx, {
|
|
613
|
+
userId: req._auth.verifiedAuth.claims.userId,
|
|
614
|
+
ownerHandle: req.ownerHandle,
|
|
615
|
+
appSlug: req.appSlug,
|
|
616
|
+
adminMode: connectionAdminMode(ctx) || req.adminMode === true,
|
|
617
|
+
});
|
|
618
|
+
if (answer.kind === "denied") {
|
|
619
|
+
await ctx.send.send(ctx, { type: "vibes.diy.res-error", error: { message: answer.message } });
|
|
587
620
|
return Result.Ok(EventoResult.Continue);
|
|
588
621
|
}
|
|
589
|
-
const t = vctx.sql.tables.appDocuments;
|
|
590
|
-
const rows = await vctx.sql.db
|
|
591
|
-
.selectDistinct({ dbName: t.dbName })
|
|
592
|
-
.from(t)
|
|
593
|
-
.where(and(eq(t.ownerHandle, req.ownerHandle), eq(t.appSlug, req.appSlug)));
|
|
594
622
|
await ctx.send.send(ctx, {
|
|
595
623
|
type: "vibes.diy.res-list-db-names",
|
|
596
624
|
status: "ok",
|
|
597
|
-
dbNames:
|
|
625
|
+
dbNames: [...answer.dbNames],
|
|
598
626
|
});
|
|
599
627
|
return Result.Ok(EventoResult.Continue);
|
|
600
628
|
}),
|