@tthr/vue 0.0.21 → 0.0.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.
|
@@ -1,11 +1,203 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-side mutation handler
|
|
3
|
-
*
|
|
3
|
+
* Executes local custom functions or proxies generic CRUD to Tether API
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { defineEventHandler, readBody, createError } from 'h3';
|
|
6
|
+
import { defineEventHandler, readBody, createError, getHeader } from 'h3';
|
|
7
7
|
import { useRuntimeConfig } from '#imports';
|
|
8
8
|
|
|
9
|
+
// Dynamic function registry - populated on first request
|
|
10
|
+
let functionRegistry = null;
|
|
11
|
+
let registryError = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Try to load the user's custom functions from ~/tether/functions
|
|
15
|
+
*/
|
|
16
|
+
async function loadFunctionRegistry() {
|
|
17
|
+
if (functionRegistry !== null || registryError !== null) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Try to import the user's functions index
|
|
23
|
+
// This path is relative to the Nuxt app's server runtime
|
|
24
|
+
const functions = await import('~~/tether/functions/index.ts').catch(() => null);
|
|
25
|
+
|
|
26
|
+
if (functions) {
|
|
27
|
+
functionRegistry = functions;
|
|
28
|
+
} else {
|
|
29
|
+
// No custom functions found - that's OK, we'll proxy everything
|
|
30
|
+
functionRegistry = {};
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Failed to load - we'll proxy everything to Tether API
|
|
34
|
+
console.warn('[Tether] Could not load custom functions:', error.message);
|
|
35
|
+
registryError = error;
|
|
36
|
+
functionRegistry = {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Look up a function by name (e.g., "channel.createChannel")
|
|
42
|
+
*/
|
|
43
|
+
function lookupFunction(name) {
|
|
44
|
+
if (!functionRegistry || !name) return null;
|
|
45
|
+
|
|
46
|
+
const parts = name.split('.');
|
|
47
|
+
if (parts.length !== 2) return null;
|
|
48
|
+
|
|
49
|
+
const [moduleName, fnName] = parts;
|
|
50
|
+
const module = functionRegistry[moduleName];
|
|
51
|
+
|
|
52
|
+
if (!module) return null;
|
|
53
|
+
|
|
54
|
+
const fn = module[fnName];
|
|
55
|
+
|
|
56
|
+
// Check if it's a valid Tether function (has a handler)
|
|
57
|
+
if (fn && typeof fn === 'object' && typeof fn.handler === 'function') {
|
|
58
|
+
return fn;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create a database proxy that routes calls to Tether's CRUD endpoints
|
|
66
|
+
*/
|
|
67
|
+
function createDatabaseProxy(apiKey, url, projectId) {
|
|
68
|
+
return new Proxy({}, {
|
|
69
|
+
get(_target, tableName) {
|
|
70
|
+
const makeRequest = async (operation, args) => {
|
|
71
|
+
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
function: `${tableName}.${operation}`,
|
|
79
|
+
args,
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
85
|
+
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await response.json();
|
|
89
|
+
return result.data;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const makeMutation = async (operation, args) => {
|
|
93
|
+
const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
function: `${tableName}.${operation}`,
|
|
101
|
+
args,
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
107
|
+
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = await response.json();
|
|
111
|
+
return result.data;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
findMany: async (options) => {
|
|
116
|
+
const args = {};
|
|
117
|
+
if (options?.where) args.where = options.where;
|
|
118
|
+
if (options?.limit) args.limit = options.limit;
|
|
119
|
+
if (options?.offset) args.offset = options.offset;
|
|
120
|
+
if (options?.orderBy) args.orderBy = options.orderBy;
|
|
121
|
+
if (options?.orderDir) args.orderDir = options.orderDir;
|
|
122
|
+
return makeRequest('list', args);
|
|
123
|
+
},
|
|
124
|
+
findFirst: async (options) => {
|
|
125
|
+
const args = { limit: 1 };
|
|
126
|
+
if (options?.where) args.where = options.where;
|
|
127
|
+
const results = await makeRequest('list', args);
|
|
128
|
+
return results?.[0] ?? null;
|
|
129
|
+
},
|
|
130
|
+
findUnique: async (options) => {
|
|
131
|
+
const args = { limit: 1 };
|
|
132
|
+
if (options?.where) args.where = options.where;
|
|
133
|
+
const results = await makeRequest('list', args);
|
|
134
|
+
return results?.[0] ?? null;
|
|
135
|
+
},
|
|
136
|
+
findById: async (id) => {
|
|
137
|
+
return makeRequest('get', { id });
|
|
138
|
+
},
|
|
139
|
+
count: async (options) => {
|
|
140
|
+
const args = {};
|
|
141
|
+
if (options?.where) args.where = options.where;
|
|
142
|
+
const result = await makeRequest('count', args);
|
|
143
|
+
return result?.count ?? 0;
|
|
144
|
+
},
|
|
145
|
+
insert: async (data) => {
|
|
146
|
+
return makeMutation('create', { data });
|
|
147
|
+
},
|
|
148
|
+
insertMany: async (items) => {
|
|
149
|
+
const results = [];
|
|
150
|
+
for (const data of items) {
|
|
151
|
+
const result = await makeMutation('create', { data });
|
|
152
|
+
results.push(result);
|
|
153
|
+
}
|
|
154
|
+
return results;
|
|
155
|
+
},
|
|
156
|
+
create: async (options) => {
|
|
157
|
+
return makeMutation('create', { data: options.data });
|
|
158
|
+
},
|
|
159
|
+
update: async (options) => {
|
|
160
|
+
const whereObj = options.where;
|
|
161
|
+
const id = whereObj?.id;
|
|
162
|
+
if (!id) {
|
|
163
|
+
throw new Error('Update requires an id in the where clause');
|
|
164
|
+
}
|
|
165
|
+
const result = await makeMutation('update', { id, data: options.data });
|
|
166
|
+
return result?.rowsAffected ?? 0;
|
|
167
|
+
},
|
|
168
|
+
upsert: async (options) => {
|
|
169
|
+
const whereObj = options.where;
|
|
170
|
+
const id = whereObj?.id;
|
|
171
|
+
|
|
172
|
+
const existing = id
|
|
173
|
+
? await makeRequest('get', { id }).catch(() => null)
|
|
174
|
+
: null;
|
|
175
|
+
|
|
176
|
+
if (existing) {
|
|
177
|
+
await makeMutation('update', { id, data: options.update });
|
|
178
|
+
return { ...existing, ...options.update };
|
|
179
|
+
} else {
|
|
180
|
+
return makeMutation('create', { data: options.create });
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
delete: async (options) => {
|
|
184
|
+
const whereObj = options.where;
|
|
185
|
+
const id = whereObj?.id;
|
|
186
|
+
if (!id) {
|
|
187
|
+
throw new Error('Delete requires an id in the where clause');
|
|
188
|
+
}
|
|
189
|
+
const result = await makeMutation('delete', { id });
|
|
190
|
+
return result?.rowsAffected ?? 0;
|
|
191
|
+
},
|
|
192
|
+
deleteById: async (id) => {
|
|
193
|
+
const result = await makeMutation('delete', { id });
|
|
194
|
+
return (result?.rowsAffected ?? 0) > 0;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
9
201
|
export default defineEventHandler(async (event) => {
|
|
10
202
|
const config = useRuntimeConfig();
|
|
11
203
|
|
|
@@ -37,6 +229,76 @@ export default defineEventHandler(async (event) => {
|
|
|
37
229
|
});
|
|
38
230
|
}
|
|
39
231
|
|
|
232
|
+
// Load function registry (once)
|
|
233
|
+
await loadFunctionRegistry();
|
|
234
|
+
|
|
235
|
+
// Try to find a custom function
|
|
236
|
+
const customFn = lookupFunction(body.function);
|
|
237
|
+
|
|
238
|
+
if (customFn) {
|
|
239
|
+
// Execute locally with database proxy
|
|
240
|
+
try {
|
|
241
|
+
const db = createDatabaseProxy(apiKey, url, projectId);
|
|
242
|
+
|
|
243
|
+
// Create auth context - try to get user identity from request
|
|
244
|
+
let userIdentity = null;
|
|
245
|
+
|
|
246
|
+
// Check for Strands auth token in cookie or header
|
|
247
|
+
const authHeader = getHeader(event, 'authorization');
|
|
248
|
+
const strandsToken = getHeader(event, 'x-strands-token');
|
|
249
|
+
|
|
250
|
+
// If using Strands auth, try to validate the token
|
|
251
|
+
if (strandsToken || authHeader?.startsWith('Bearer ')) {
|
|
252
|
+
const token = strandsToken || authHeader?.replace('Bearer ', '');
|
|
253
|
+
if (token) {
|
|
254
|
+
try {
|
|
255
|
+
// Try to fetch user identity from Strands auth service
|
|
256
|
+
const authResponse = await fetch('https://accounts.strands.gg/api/auth/me', {
|
|
257
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
258
|
+
});
|
|
259
|
+
if (authResponse.ok) {
|
|
260
|
+
const userData = await authResponse.json();
|
|
261
|
+
userIdentity = {
|
|
262
|
+
subject: userData.id || userData.sub,
|
|
263
|
+
email: userData.email,
|
|
264
|
+
...userData,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// Auth validation failed - continue without identity
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const auth = {
|
|
274
|
+
getUserIdentity: async () => userIdentity,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const ctx = {
|
|
278
|
+
auth: {
|
|
279
|
+
userId: userIdentity?.subject ?? null,
|
|
280
|
+
claims: userIdentity ?? {},
|
|
281
|
+
},
|
|
282
|
+
userId: userIdentity?.subject ?? null,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const result = await customFn.handler({
|
|
286
|
+
db,
|
|
287
|
+
ctx,
|
|
288
|
+
auth,
|
|
289
|
+
args: body.args ?? {},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return { data: result };
|
|
293
|
+
} catch (error) {
|
|
294
|
+
throw createError({
|
|
295
|
+
statusCode: 500,
|
|
296
|
+
message: error.message || 'Mutation execution failed',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// No custom function found - proxy to Tether API (generic CRUD)
|
|
40
302
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
|
|
41
303
|
method: 'POST',
|
|
42
304
|
headers: {
|
|
@@ -1,11 +1,203 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-side query handler
|
|
3
|
-
*
|
|
3
|
+
* Executes local custom functions or proxies generic CRUD to Tether API
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { defineEventHandler, readBody, createError } from 'h3';
|
|
6
|
+
import { defineEventHandler, readBody, createError, getHeader } from 'h3';
|
|
7
7
|
import { useRuntimeConfig } from '#imports';
|
|
8
8
|
|
|
9
|
+
// Dynamic function registry - populated on first request
|
|
10
|
+
let functionRegistry = null;
|
|
11
|
+
let registryError = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Try to load the user's custom functions from ~/tether/functions
|
|
15
|
+
*/
|
|
16
|
+
async function loadFunctionRegistry() {
|
|
17
|
+
if (functionRegistry !== null || registryError !== null) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Try to import the user's functions index
|
|
23
|
+
// This path is relative to the Nuxt app's server runtime
|
|
24
|
+
const functions = await import('~~/tether/functions/index.ts').catch(() => null);
|
|
25
|
+
|
|
26
|
+
if (functions) {
|
|
27
|
+
functionRegistry = functions;
|
|
28
|
+
} else {
|
|
29
|
+
// No custom functions found - that's OK, we'll proxy everything
|
|
30
|
+
functionRegistry = {};
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Failed to load - we'll proxy everything to Tether API
|
|
34
|
+
console.warn('[Tether] Could not load custom functions:', error.message);
|
|
35
|
+
registryError = error;
|
|
36
|
+
functionRegistry = {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Look up a function by name (e.g., "channel.getMyChannels")
|
|
42
|
+
*/
|
|
43
|
+
function lookupFunction(name) {
|
|
44
|
+
if (!functionRegistry || !name) return null;
|
|
45
|
+
|
|
46
|
+
const parts = name.split('.');
|
|
47
|
+
if (parts.length !== 2) return null;
|
|
48
|
+
|
|
49
|
+
const [moduleName, fnName] = parts;
|
|
50
|
+
const module = functionRegistry[moduleName];
|
|
51
|
+
|
|
52
|
+
if (!module) return null;
|
|
53
|
+
|
|
54
|
+
const fn = module[fnName];
|
|
55
|
+
|
|
56
|
+
// Check if it's a valid Tether function (has a handler)
|
|
57
|
+
if (fn && typeof fn === 'object' && typeof fn.handler === 'function') {
|
|
58
|
+
return fn;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create a database proxy that routes calls to Tether's CRUD endpoints
|
|
66
|
+
*/
|
|
67
|
+
function createDatabaseProxy(apiKey, url, projectId) {
|
|
68
|
+
return new Proxy({}, {
|
|
69
|
+
get(_target, tableName) {
|
|
70
|
+
const makeRequest = async (operation, args) => {
|
|
71
|
+
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
function: `${tableName}.${operation}`,
|
|
79
|
+
args,
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
85
|
+
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await response.json();
|
|
89
|
+
return result.data;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const makeMutation = async (operation, args) => {
|
|
93
|
+
const response = await fetch(`${url}/api/v1/projects/${projectId}/mutation`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
function: `${tableName}.${operation}`,
|
|
101
|
+
args,
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
107
|
+
throw new Error(error.error || `Database operation failed: ${tableName}.${operation}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = await response.json();
|
|
111
|
+
return result.data;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
findMany: async (options) => {
|
|
116
|
+
const args = {};
|
|
117
|
+
if (options?.where) args.where = options.where;
|
|
118
|
+
if (options?.limit) args.limit = options.limit;
|
|
119
|
+
if (options?.offset) args.offset = options.offset;
|
|
120
|
+
if (options?.orderBy) args.orderBy = options.orderBy;
|
|
121
|
+
if (options?.orderDir) args.orderDir = options.orderDir;
|
|
122
|
+
return makeRequest('list', args);
|
|
123
|
+
},
|
|
124
|
+
findFirst: async (options) => {
|
|
125
|
+
const args = { limit: 1 };
|
|
126
|
+
if (options?.where) args.where = options.where;
|
|
127
|
+
const results = await makeRequest('list', args);
|
|
128
|
+
return results?.[0] ?? null;
|
|
129
|
+
},
|
|
130
|
+
findUnique: async (options) => {
|
|
131
|
+
const args = { limit: 1 };
|
|
132
|
+
if (options?.where) args.where = options.where;
|
|
133
|
+
const results = await makeRequest('list', args);
|
|
134
|
+
return results?.[0] ?? null;
|
|
135
|
+
},
|
|
136
|
+
findById: async (id) => {
|
|
137
|
+
return makeRequest('get', { id });
|
|
138
|
+
},
|
|
139
|
+
count: async (options) => {
|
|
140
|
+
const args = {};
|
|
141
|
+
if (options?.where) args.where = options.where;
|
|
142
|
+
const result = await makeRequest('count', args);
|
|
143
|
+
return result?.count ?? 0;
|
|
144
|
+
},
|
|
145
|
+
insert: async (data) => {
|
|
146
|
+
return makeMutation('create', { data });
|
|
147
|
+
},
|
|
148
|
+
insertMany: async (items) => {
|
|
149
|
+
const results = [];
|
|
150
|
+
for (const data of items) {
|
|
151
|
+
const result = await makeMutation('create', { data });
|
|
152
|
+
results.push(result);
|
|
153
|
+
}
|
|
154
|
+
return results;
|
|
155
|
+
},
|
|
156
|
+
create: async (options) => {
|
|
157
|
+
return makeMutation('create', { data: options.data });
|
|
158
|
+
},
|
|
159
|
+
update: async (options) => {
|
|
160
|
+
const whereObj = options.where;
|
|
161
|
+
const id = whereObj?.id;
|
|
162
|
+
if (!id) {
|
|
163
|
+
throw new Error('Update requires an id in the where clause');
|
|
164
|
+
}
|
|
165
|
+
const result = await makeMutation('update', { id, data: options.data });
|
|
166
|
+
return result?.rowsAffected ?? 0;
|
|
167
|
+
},
|
|
168
|
+
upsert: async (options) => {
|
|
169
|
+
const whereObj = options.where;
|
|
170
|
+
const id = whereObj?.id;
|
|
171
|
+
|
|
172
|
+
const existing = id
|
|
173
|
+
? await makeRequest('get', { id }).catch(() => null)
|
|
174
|
+
: null;
|
|
175
|
+
|
|
176
|
+
if (existing) {
|
|
177
|
+
await makeMutation('update', { id, data: options.update });
|
|
178
|
+
return { ...existing, ...options.update };
|
|
179
|
+
} else {
|
|
180
|
+
return makeMutation('create', { data: options.create });
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
delete: async (options) => {
|
|
184
|
+
const whereObj = options.where;
|
|
185
|
+
const id = whereObj?.id;
|
|
186
|
+
if (!id) {
|
|
187
|
+
throw new Error('Delete requires an id in the where clause');
|
|
188
|
+
}
|
|
189
|
+
const result = await makeMutation('delete', { id });
|
|
190
|
+
return result?.rowsAffected ?? 0;
|
|
191
|
+
},
|
|
192
|
+
deleteById: async (id) => {
|
|
193
|
+
const result = await makeMutation('delete', { id });
|
|
194
|
+
return (result?.rowsAffected ?? 0) > 0;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
9
201
|
export default defineEventHandler(async (event) => {
|
|
10
202
|
const config = useRuntimeConfig();
|
|
11
203
|
|
|
@@ -37,6 +229,77 @@ export default defineEventHandler(async (event) => {
|
|
|
37
229
|
});
|
|
38
230
|
}
|
|
39
231
|
|
|
232
|
+
// Load function registry (once)
|
|
233
|
+
await loadFunctionRegistry();
|
|
234
|
+
|
|
235
|
+
// Try to find a custom function
|
|
236
|
+
const customFn = lookupFunction(body.function);
|
|
237
|
+
|
|
238
|
+
if (customFn) {
|
|
239
|
+
// Execute locally with database proxy
|
|
240
|
+
try {
|
|
241
|
+
const db = createDatabaseProxy(apiKey, url, projectId);
|
|
242
|
+
|
|
243
|
+
// Create auth context - try to get user identity from request
|
|
244
|
+
// The app should pass auth info via a custom header or we check for common auth patterns
|
|
245
|
+
let userIdentity = null;
|
|
246
|
+
|
|
247
|
+
// Check for Strands auth token in cookie or header
|
|
248
|
+
const authHeader = getHeader(event, 'authorization');
|
|
249
|
+
const strandsToken = getHeader(event, 'x-strands-token');
|
|
250
|
+
|
|
251
|
+
// If using Strands auth, try to validate the token
|
|
252
|
+
if (strandsToken || authHeader?.startsWith('Bearer ')) {
|
|
253
|
+
const token = strandsToken || authHeader?.replace('Bearer ', '');
|
|
254
|
+
if (token) {
|
|
255
|
+
try {
|
|
256
|
+
// Try to fetch user identity from Strands auth service
|
|
257
|
+
const authResponse = await fetch('https://accounts.strands.gg/api/auth/me', {
|
|
258
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
259
|
+
});
|
|
260
|
+
if (authResponse.ok) {
|
|
261
|
+
const userData = await authResponse.json();
|
|
262
|
+
userIdentity = {
|
|
263
|
+
subject: userData.id || userData.sub,
|
|
264
|
+
email: userData.email,
|
|
265
|
+
...userData,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
// Auth validation failed - continue without identity
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const auth = {
|
|
275
|
+
getUserIdentity: async () => userIdentity,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const ctx = {
|
|
279
|
+
auth: {
|
|
280
|
+
userId: userIdentity?.subject ?? null,
|
|
281
|
+
claims: userIdentity ?? {},
|
|
282
|
+
},
|
|
283
|
+
userId: userIdentity?.subject ?? null,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const result = await customFn.handler({
|
|
287
|
+
db,
|
|
288
|
+
ctx,
|
|
289
|
+
auth,
|
|
290
|
+
args: body.args ?? {},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return { data: result };
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw createError({
|
|
296
|
+
statusCode: 500,
|
|
297
|
+
message: error.message || 'Function execution failed',
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// No custom function found - proxy to Tether API (generic CRUD)
|
|
40
303
|
const response = await fetch(`${url}/api/v1/projects/${projectId}/query`, {
|
|
41
304
|
method: 'POST',
|
|
42
305
|
headers: {
|