@meetploy/cli 1.12.1 → 1.13.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/dist/dashboard-dist/assets/main-Bj2g1WCI.js +324 -0
- package/dist/dashboard-dist/assets/main-q4PKg1PN.css +1 -0
- package/dist/dashboard-dist/index.html +2 -2
- package/dist/dev.js +652 -12
- package/dist/index.js +800 -34
- package/package.json +1 -1
- package/dist/dashboard-dist/assets/main-BNiZvT9K.css +0 -1
- package/dist/dashboard-dist/assets/main-CYxpKFOS.js +0 -304
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { mkdir, writeFile, readFile, chmod, access } from 'fs/promises';
|
|
4
|
-
import { join, dirname, relative } from 'path';
|
|
4
|
+
import { join, dirname, relative, basename } from 'path';
|
|
5
5
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, readFile as readFile$1, mkdirSync } from 'fs';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { parse } from 'yaml';
|
|
8
8
|
import { build } from 'esbuild';
|
|
9
9
|
import { watch } from 'chokidar';
|
|
10
|
+
import { randomBytes, randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, createHash } from 'crypto';
|
|
11
|
+
import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
|
|
10
12
|
import { URL, fileURLToPath } from 'url';
|
|
11
|
-
import { randomBytes, randomUUID, createHash } from 'crypto';
|
|
12
13
|
import { serve } from '@hono/node-server';
|
|
13
14
|
import { Hono } from 'hono';
|
|
14
15
|
import { homedir, tmpdir } from 'os';
|
|
@@ -195,6 +196,7 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
|
|
|
195
196
|
validatedConfig.main = validateRelativePath(config.main, "main", configFile);
|
|
196
197
|
validateBindings(config.db, "db", configFile);
|
|
197
198
|
validateBindings(config.queue, "queue", configFile);
|
|
199
|
+
validateBindings(config.cache, "cache", configFile);
|
|
198
200
|
validateBindings(config.workflow, "workflow", configFile);
|
|
199
201
|
if (config.ai !== void 0 && typeof config.ai !== "boolean") {
|
|
200
202
|
throw new Error(`'ai' in ${configFile} must be a boolean`);
|
|
@@ -220,6 +222,20 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
|
|
|
220
222
|
throw new Error(`'compatibility_date' in ${configFile} must be in YYYY-MM-DD format (e.g., 2025-04-02)`);
|
|
221
223
|
}
|
|
222
224
|
}
|
|
225
|
+
if (config.auth !== void 0) {
|
|
226
|
+
if (typeof config.auth !== "object" || config.auth === null) {
|
|
227
|
+
throw new Error(`'auth' in ${configFile} must be an object`);
|
|
228
|
+
}
|
|
229
|
+
if (!config.auth.binding) {
|
|
230
|
+
throw new Error(`'auth.binding' is required in ${configFile} when auth is configured`);
|
|
231
|
+
}
|
|
232
|
+
if (typeof config.auth.binding !== "string") {
|
|
233
|
+
throw new Error(`'auth.binding' in ${configFile} must be a string`);
|
|
234
|
+
}
|
|
235
|
+
if (!BINDING_NAME_REGEX.test(config.auth.binding)) {
|
|
236
|
+
throw new Error(`Invalid auth binding name '${config.auth.binding}' in ${configFile}. Binding names must be uppercase with underscores (e.g., AUTH_DB, AUTH)`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
223
239
|
return validatedConfig;
|
|
224
240
|
}
|
|
225
241
|
async function readPloyConfig(projectDir, configPath) {
|
|
@@ -260,7 +276,7 @@ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions
|
|
|
260
276
|
return validatePloyConfig(config, configFile, validationOptions);
|
|
261
277
|
}
|
|
262
278
|
function hasBindings(config) {
|
|
263
|
-
return !!(config.db || config.queue || config.workflow || config.ai);
|
|
279
|
+
return !!(config.db || config.queue || config.cache || config.workflow || config.ai || config.auth);
|
|
264
280
|
}
|
|
265
281
|
function getWorkerEntryPoint(projectDir, config) {
|
|
266
282
|
if (config.main) {
|
|
@@ -316,6 +332,66 @@ var init_cli = __esm({
|
|
|
316
332
|
}
|
|
317
333
|
});
|
|
318
334
|
|
|
335
|
+
// ../emulator/dist/runtime/cache-runtime.js
|
|
336
|
+
var CACHE_RUNTIME_CODE;
|
|
337
|
+
var init_cache_runtime = __esm({
|
|
338
|
+
"../emulator/dist/runtime/cache-runtime.js"() {
|
|
339
|
+
CACHE_RUNTIME_CODE = `
|
|
340
|
+
interface CacheBinding {
|
|
341
|
+
get: (key: string) => Promise<string | null>;
|
|
342
|
+
set: (key: string, value: string, opts: { ttl: number }) => Promise<void>;
|
|
343
|
+
delete: (key: string) => Promise<void>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function initializeCache(cacheName: string, serviceUrl: string): CacheBinding {
|
|
347
|
+
return {
|
|
348
|
+
async get(key: string): Promise<string | null> {
|
|
349
|
+
const response = await fetch(serviceUrl + "/cache/get", {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: { "Content-Type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ cacheName, key }),
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
const errorText = await response.text();
|
|
357
|
+
throw new Error("Cache get failed: " + errorText);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const result = await response.json();
|
|
361
|
+
return result.value ?? null;
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
async set(key: string, value: string, opts: { ttl: number }): Promise<void> {
|
|
365
|
+
const response = await fetch(serviceUrl + "/cache/set", {
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: { "Content-Type": "application/json" },
|
|
368
|
+
body: JSON.stringify({ cacheName, key, value, ttl: opts.ttl }),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
const errorText = await response.text();
|
|
373
|
+
throw new Error("Cache set failed: " + errorText);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
async delete(key: string): Promise<void> {
|
|
378
|
+
const response = await fetch(serviceUrl + "/cache/delete", {
|
|
379
|
+
method: "POST",
|
|
380
|
+
headers: { "Content-Type": "application/json" },
|
|
381
|
+
body: JSON.stringify({ cacheName, key }),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
const errorText = await response.text();
|
|
386
|
+
throw new Error("Cache delete failed: " + errorText);
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
319
395
|
// ../shared/dist/d1-runtime.js
|
|
320
396
|
var DB_RUNTIME_CODE, DB_RUNTIME_CODE_PRODUCTION;
|
|
321
397
|
var init_d1_runtime = __esm({
|
|
@@ -597,6 +673,12 @@ var init_trace_event = __esm({
|
|
|
597
673
|
}
|
|
598
674
|
});
|
|
599
675
|
|
|
676
|
+
// ../shared/dist/trace-id.js
|
|
677
|
+
var init_trace_id = __esm({
|
|
678
|
+
"../shared/dist/trace-id.js"() {
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
600
682
|
// ../shared/dist/unified-log.js
|
|
601
683
|
var init_unified_log = __esm({
|
|
602
684
|
"../shared/dist/unified-log.js"() {
|
|
@@ -616,6 +698,7 @@ var init_dist = __esm({
|
|
|
616
698
|
init_error();
|
|
617
699
|
init_health_check();
|
|
618
700
|
init_trace_event();
|
|
701
|
+
init_trace_id();
|
|
619
702
|
init_unified_log();
|
|
620
703
|
init_url_validation();
|
|
621
704
|
}
|
|
@@ -920,6 +1003,12 @@ function generateWrapperCode(config, mockServiceUrl) {
|
|
|
920
1003
|
bindings.push(` ${bindingName}: initializeQueue("${queueName}", "${mockServiceUrl}"),`);
|
|
921
1004
|
}
|
|
922
1005
|
}
|
|
1006
|
+
if (config.cache) {
|
|
1007
|
+
imports.push('import { initializeCache } from "__ploy_cache_runtime__";');
|
|
1008
|
+
for (const [bindingName, cacheName] of Object.entries(config.cache)) {
|
|
1009
|
+
bindings.push(` ${bindingName}: initializeCache("${cacheName}", "${mockServiceUrl}"),`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
923
1012
|
if (config.workflow) {
|
|
924
1013
|
imports.push('import { initializeWorkflow, createStepContext, executeWorkflow } from "__ploy_workflow_runtime__";');
|
|
925
1014
|
for (const [bindingName, workflowName] of Object.entries(config.workflow)) {
|
|
@@ -1023,6 +1112,10 @@ function createRuntimePlugin(_config) {
|
|
|
1023
1112
|
path: "__ploy_queue_runtime__",
|
|
1024
1113
|
namespace: "ploy-runtime"
|
|
1025
1114
|
}));
|
|
1115
|
+
build2.onResolve({ filter: /^__ploy_cache_runtime__$/ }, () => ({
|
|
1116
|
+
path: "__ploy_cache_runtime__",
|
|
1117
|
+
namespace: "ploy-runtime"
|
|
1118
|
+
}));
|
|
1026
1119
|
build2.onResolve({ filter: /^__ploy_workflow_runtime__$/ }, () => ({
|
|
1027
1120
|
path: "__ploy_workflow_runtime__",
|
|
1028
1121
|
namespace: "ploy-runtime"
|
|
@@ -1035,6 +1128,10 @@ function createRuntimePlugin(_config) {
|
|
|
1035
1128
|
contents: QUEUE_RUNTIME_CODE,
|
|
1036
1129
|
loader: "ts"
|
|
1037
1130
|
}));
|
|
1131
|
+
build2.onLoad({ filter: /^__ploy_cache_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
1132
|
+
contents: CACHE_RUNTIME_CODE,
|
|
1133
|
+
loader: "ts"
|
|
1134
|
+
}));
|
|
1038
1135
|
build2.onLoad({ filter: /^__ploy_workflow_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
1039
1136
|
contents: WORKFLOW_RUNTIME_CODE,
|
|
1040
1137
|
loader: "ts"
|
|
@@ -1075,6 +1172,7 @@ async function bundleWorker(options) {
|
|
|
1075
1172
|
var NODE_BUILTINS;
|
|
1076
1173
|
var init_bundler = __esm({
|
|
1077
1174
|
"../emulator/dist/bundler/bundler.js"() {
|
|
1175
|
+
init_cache_runtime();
|
|
1078
1176
|
init_db_runtime();
|
|
1079
1177
|
init_queue_runtime();
|
|
1080
1178
|
init_workflow_runtime();
|
|
@@ -1134,9 +1232,6 @@ function log(message) {
|
|
|
1134
1232
|
function success(message) {
|
|
1135
1233
|
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.green}[ploy]${COLORS.reset} ${message}`);
|
|
1136
1234
|
}
|
|
1137
|
-
function warn(message) {
|
|
1138
|
-
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}[ploy]${COLORS.reset} ${message}`);
|
|
1139
|
-
}
|
|
1140
1235
|
function error(message) {
|
|
1141
1236
|
console.error(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.red}[ploy]${COLORS.reset} ${message}`);
|
|
1142
1237
|
}
|
|
@@ -1159,6 +1254,22 @@ var init_logger = __esm({
|
|
|
1159
1254
|
};
|
|
1160
1255
|
}
|
|
1161
1256
|
});
|
|
1257
|
+
function isIgnored(filePath) {
|
|
1258
|
+
const name = basename(filePath);
|
|
1259
|
+
if (name.startsWith(".")) {
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
if (IGNORED_DIRS.has(name)) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
if (IGNORED_FILES.has(name)) {
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
if (name.endsWith(".log")) {
|
|
1269
|
+
return true;
|
|
1270
|
+
}
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1162
1273
|
function createFileWatcher(srcDir, onRebuild) {
|
|
1163
1274
|
let watcher = null;
|
|
1164
1275
|
let debounceTimer = null;
|
|
@@ -1191,25 +1302,11 @@ function createFileWatcher(srcDir, onRebuild) {
|
|
|
1191
1302
|
watcher = watch(srcDir, {
|
|
1192
1303
|
persistent: true,
|
|
1193
1304
|
ignoreInitial: true,
|
|
1194
|
-
ignored:
|
|
1195
|
-
"**/node_modules/**",
|
|
1196
|
-
"**/dist/**",
|
|
1197
|
-
"**/.*",
|
|
1198
|
-
"**/.*/**",
|
|
1199
|
-
"**/coverage/**",
|
|
1200
|
-
"**/build/**",
|
|
1201
|
-
"**/*.log",
|
|
1202
|
-
"**/pnpm-lock.yaml",
|
|
1203
|
-
"**/package-lock.json",
|
|
1204
|
-
"**/yarn.lock"
|
|
1205
|
-
]
|
|
1305
|
+
ignored: isIgnored
|
|
1206
1306
|
});
|
|
1207
1307
|
watcher.on("error", (err) => {
|
|
1208
1308
|
const error2 = err;
|
|
1209
|
-
if (error2.code
|
|
1210
|
-
warn("Warning: Too many open files. Some file changes may not be detected.");
|
|
1211
|
-
warn("Consider increasing your system's file descriptor limit (ulimit -n).");
|
|
1212
|
-
} else {
|
|
1309
|
+
if (error2.code !== "EMFILE") {
|
|
1213
1310
|
error(`File watcher error: ${error2.message || String(err)}`);
|
|
1214
1311
|
}
|
|
1215
1312
|
});
|
|
@@ -1241,9 +1338,16 @@ function createFileWatcher(srcDir, onRebuild) {
|
|
|
1241
1338
|
}
|
|
1242
1339
|
};
|
|
1243
1340
|
}
|
|
1341
|
+
var IGNORED_DIRS, IGNORED_FILES;
|
|
1244
1342
|
var init_watcher = __esm({
|
|
1245
1343
|
"../emulator/dist/bundler/watcher.js"() {
|
|
1246
1344
|
init_logger();
|
|
1345
|
+
IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage", "build"]);
|
|
1346
|
+
IGNORED_FILES = /* @__PURE__ */ new Set([
|
|
1347
|
+
"pnpm-lock.yaml",
|
|
1348
|
+
"package-lock.json",
|
|
1349
|
+
"yarn.lock"
|
|
1350
|
+
]);
|
|
1247
1351
|
}
|
|
1248
1352
|
});
|
|
1249
1353
|
|
|
@@ -1316,6 +1420,289 @@ var init_workerd_config = __esm({
|
|
|
1316
1420
|
"../emulator/dist/config/workerd-config.js"() {
|
|
1317
1421
|
}
|
|
1318
1422
|
});
|
|
1423
|
+
function generateId() {
|
|
1424
|
+
return randomBytes(16).toString("hex");
|
|
1425
|
+
}
|
|
1426
|
+
function hashPassword(password) {
|
|
1427
|
+
const salt = randomBytes(32).toString("hex");
|
|
1428
|
+
const hash = pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
1429
|
+
return `${salt}:${hash}`;
|
|
1430
|
+
}
|
|
1431
|
+
function verifyPassword(password, storedHash) {
|
|
1432
|
+
const [salt, hash] = storedHash.split(":");
|
|
1433
|
+
const derivedHash = pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
1434
|
+
return timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(derivedHash, "hex"));
|
|
1435
|
+
}
|
|
1436
|
+
function hashToken(token) {
|
|
1437
|
+
return createHmac("sha256", "emulator-secret").update(token).digest("hex");
|
|
1438
|
+
}
|
|
1439
|
+
function base64UrlEncode(str) {
|
|
1440
|
+
return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1441
|
+
}
|
|
1442
|
+
function base64UrlDecode(str) {
|
|
1443
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
1444
|
+
while (base64.length % 4) {
|
|
1445
|
+
base64 += "=";
|
|
1446
|
+
}
|
|
1447
|
+
return Buffer.from(base64, "base64").toString();
|
|
1448
|
+
}
|
|
1449
|
+
function createJWT(payload) {
|
|
1450
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
1451
|
+
const headerB64 = base64UrlEncode(JSON.stringify(header));
|
|
1452
|
+
const payloadB64 = base64UrlEncode(JSON.stringify(payload));
|
|
1453
|
+
const signature = createHmac("sha256", JWT_SECRET).update(`${headerB64}.${payloadB64}`).digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1454
|
+
return `${headerB64}.${payloadB64}.${signature}`;
|
|
1455
|
+
}
|
|
1456
|
+
function verifyJWT(token) {
|
|
1457
|
+
try {
|
|
1458
|
+
const parts = token.split(".");
|
|
1459
|
+
if (parts.length !== 3) {
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
const [headerB64, payloadB64, signature] = parts;
|
|
1463
|
+
const expectedSig = createHmac("sha256", JWT_SECRET).update(`${headerB64}.${payloadB64}`).digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1464
|
+
if (signature !== expectedSig) {
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
const payload = JSON.parse(base64UrlDecode(payloadB64));
|
|
1468
|
+
if (payload.exp < Math.floor(Date.now() / 1e3)) {
|
|
1469
|
+
return null;
|
|
1470
|
+
}
|
|
1471
|
+
return payload;
|
|
1472
|
+
} catch {
|
|
1473
|
+
return null;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function createSessionToken(userId, email) {
|
|
1477
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1478
|
+
const sessionId = generateId();
|
|
1479
|
+
const token = createJWT({
|
|
1480
|
+
sub: userId,
|
|
1481
|
+
email,
|
|
1482
|
+
iat: now,
|
|
1483
|
+
exp: now + SESSION_TOKEN_EXPIRY,
|
|
1484
|
+
jti: sessionId
|
|
1485
|
+
});
|
|
1486
|
+
return {
|
|
1487
|
+
token,
|
|
1488
|
+
sessionId,
|
|
1489
|
+
expiresAt: new Date((now + SESSION_TOKEN_EXPIRY) * 1e3)
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
function validateEmail(email) {
|
|
1493
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1494
|
+
if (!emailRegex.test(email)) {
|
|
1495
|
+
return "Invalid email format";
|
|
1496
|
+
}
|
|
1497
|
+
return null;
|
|
1498
|
+
}
|
|
1499
|
+
function validatePassword(password) {
|
|
1500
|
+
if (password.length < 8) {
|
|
1501
|
+
return "Password must be at least 8 characters";
|
|
1502
|
+
}
|
|
1503
|
+
return null;
|
|
1504
|
+
}
|
|
1505
|
+
function setSessionCookie(c, sessionToken) {
|
|
1506
|
+
setCookie(c, "ploy_session", sessionToken, {
|
|
1507
|
+
httpOnly: true,
|
|
1508
|
+
secure: false,
|
|
1509
|
+
sameSite: "Lax",
|
|
1510
|
+
path: "/",
|
|
1511
|
+
maxAge: SESSION_TOKEN_EXPIRY
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
function clearSessionCookie(c) {
|
|
1515
|
+
deleteCookie(c, "ploy_session", { path: "/" });
|
|
1516
|
+
}
|
|
1517
|
+
function createAuthHandlers(db) {
|
|
1518
|
+
const signupHandler = async (c) => {
|
|
1519
|
+
try {
|
|
1520
|
+
const body = await c.req.json();
|
|
1521
|
+
const { email, password, metadata } = body;
|
|
1522
|
+
const emailError = validateEmail(email);
|
|
1523
|
+
if (emailError) {
|
|
1524
|
+
return c.json({ error: emailError }, 400);
|
|
1525
|
+
}
|
|
1526
|
+
const passwordError = validatePassword(password);
|
|
1527
|
+
if (passwordError) {
|
|
1528
|
+
return c.json({ error: passwordError }, 400);
|
|
1529
|
+
}
|
|
1530
|
+
const existingUser = db.prepare("SELECT id FROM auth_users WHERE email = ?").get(email.toLowerCase());
|
|
1531
|
+
if (existingUser) {
|
|
1532
|
+
return c.json({ error: "User already exists" }, 409);
|
|
1533
|
+
}
|
|
1534
|
+
const userId = generateId();
|
|
1535
|
+
const passwordHash = hashPassword(password);
|
|
1536
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1537
|
+
db.prepare(`INSERT INTO auth_users (id, email, password_hash, created_at, updated_at, metadata)
|
|
1538
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(userId, email.toLowerCase(), passwordHash, now, now, metadata ? JSON.stringify(metadata) : null);
|
|
1539
|
+
const { token: sessionToken, sessionId, expiresAt } = createSessionToken(userId, email.toLowerCase());
|
|
1540
|
+
const sessionTokenHash = hashToken(sessionToken);
|
|
1541
|
+
db.prepare(`INSERT INTO auth_sessions (id, user_id, token_hash, expires_at, created_at)
|
|
1542
|
+
VALUES (?, ?, ?, ?, ?)`).run(sessionId, userId, sessionTokenHash, expiresAt.toISOString(), now);
|
|
1543
|
+
setSessionCookie(c, sessionToken);
|
|
1544
|
+
return c.json({
|
|
1545
|
+
user: {
|
|
1546
|
+
id: userId,
|
|
1547
|
+
email: email.toLowerCase(),
|
|
1548
|
+
emailVerified: false,
|
|
1549
|
+
createdAt: now,
|
|
1550
|
+
metadata: metadata ?? null
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1555
|
+
return c.json({ error: message }, 500);
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
const signinHandler = async (c) => {
|
|
1559
|
+
try {
|
|
1560
|
+
const body = await c.req.json();
|
|
1561
|
+
const { email, password } = body;
|
|
1562
|
+
const user = db.prepare("SELECT * FROM auth_users WHERE email = ?").get(email.toLowerCase());
|
|
1563
|
+
if (!user) {
|
|
1564
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
1565
|
+
}
|
|
1566
|
+
if (!verifyPassword(password, user.password_hash)) {
|
|
1567
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
1568
|
+
}
|
|
1569
|
+
const { token: sessionToken, sessionId, expiresAt } = createSessionToken(user.id, user.email);
|
|
1570
|
+
const sessionTokenHash = hashToken(sessionToken);
|
|
1571
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1572
|
+
db.prepare(`INSERT INTO auth_sessions (id, user_id, token_hash, expires_at, created_at)
|
|
1573
|
+
VALUES (?, ?, ?, ?, ?)`).run(sessionId, user.id, sessionTokenHash, expiresAt.toISOString(), now);
|
|
1574
|
+
let metadata = null;
|
|
1575
|
+
if (user.metadata) {
|
|
1576
|
+
try {
|
|
1577
|
+
metadata = JSON.parse(user.metadata);
|
|
1578
|
+
} catch {
|
|
1579
|
+
metadata = null;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
setSessionCookie(c, sessionToken);
|
|
1583
|
+
return c.json({
|
|
1584
|
+
user: {
|
|
1585
|
+
id: user.id,
|
|
1586
|
+
email: user.email,
|
|
1587
|
+
emailVerified: user.email_verified === 1,
|
|
1588
|
+
createdAt: user.created_at,
|
|
1589
|
+
metadata
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1594
|
+
return c.json({ error: message }, 500);
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
const meHandler = async (c) => {
|
|
1598
|
+
try {
|
|
1599
|
+
const cookieToken = getCookie(c, "ploy_session");
|
|
1600
|
+
const authHeader = c.req.header("Authorization");
|
|
1601
|
+
let token;
|
|
1602
|
+
if (cookieToken) {
|
|
1603
|
+
token = cookieToken;
|
|
1604
|
+
} else if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
1605
|
+
token = authHeader.slice(7);
|
|
1606
|
+
}
|
|
1607
|
+
if (!token) {
|
|
1608
|
+
return c.json({ error: "Missing authentication" }, 401);
|
|
1609
|
+
}
|
|
1610
|
+
const payload = verifyJWT(token);
|
|
1611
|
+
if (!payload) {
|
|
1612
|
+
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1613
|
+
}
|
|
1614
|
+
const user = db.prepare("SELECT id, email, email_verified, created_at, updated_at, metadata FROM auth_users WHERE id = ?").get(payload.sub);
|
|
1615
|
+
if (!user) {
|
|
1616
|
+
return c.json({ error: "User not found" }, 401);
|
|
1617
|
+
}
|
|
1618
|
+
let metadata = null;
|
|
1619
|
+
if (user.metadata) {
|
|
1620
|
+
try {
|
|
1621
|
+
metadata = JSON.parse(user.metadata);
|
|
1622
|
+
} catch {
|
|
1623
|
+
metadata = null;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return c.json({
|
|
1627
|
+
user: {
|
|
1628
|
+
id: user.id,
|
|
1629
|
+
email: user.email,
|
|
1630
|
+
emailVerified: user.email_verified === 1,
|
|
1631
|
+
createdAt: user.created_at,
|
|
1632
|
+
updatedAt: user.updated_at,
|
|
1633
|
+
metadata
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1638
|
+
return c.json({ error: message }, 500);
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
const signoutHandler = async (c) => {
|
|
1642
|
+
try {
|
|
1643
|
+
const sessionToken = getCookie(c, "ploy_session");
|
|
1644
|
+
if (sessionToken) {
|
|
1645
|
+
const payload = verifyJWT(sessionToken);
|
|
1646
|
+
if (payload) {
|
|
1647
|
+
const tokenHash = hashToken(sessionToken);
|
|
1648
|
+
db.prepare("UPDATE auth_sessions SET revoked = 1 WHERE token_hash = ?").run(tokenHash);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
clearSessionCookie(c);
|
|
1652
|
+
return c.json({ success: true });
|
|
1653
|
+
} catch (err) {
|
|
1654
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1655
|
+
return c.json({ error: message }, 500);
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
return {
|
|
1659
|
+
signupHandler,
|
|
1660
|
+
signinHandler,
|
|
1661
|
+
meHandler,
|
|
1662
|
+
signoutHandler
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
var JWT_SECRET, SESSION_TOKEN_EXPIRY;
|
|
1666
|
+
var init_auth_service = __esm({
|
|
1667
|
+
"../emulator/dist/services/auth-service.js"() {
|
|
1668
|
+
JWT_SECRET = "ploy-emulator-dev-secret";
|
|
1669
|
+
SESSION_TOKEN_EXPIRY = 7 * 24 * 60 * 60;
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
// ../emulator/dist/services/cache-service.js
|
|
1674
|
+
function createCacheHandlers(db) {
|
|
1675
|
+
const getHandler = async (c) => {
|
|
1676
|
+
const body = await c.req.json();
|
|
1677
|
+
const { cacheName, key } = body;
|
|
1678
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1679
|
+
const row = db.prepare(`SELECT value FROM cache_entries WHERE cache_name = ? AND key = ? AND expires_at > ?`).get(cacheName, key, now);
|
|
1680
|
+
return c.json({ value: row?.value ?? null });
|
|
1681
|
+
};
|
|
1682
|
+
const setHandler = async (c) => {
|
|
1683
|
+
const body = await c.req.json();
|
|
1684
|
+
const { cacheName, key, value, ttl } = body;
|
|
1685
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1686
|
+
const expiresAt = now + ttl;
|
|
1687
|
+
db.prepare(`INSERT OR REPLACE INTO cache_entries (cache_name, key, value, expires_at) VALUES (?, ?, ?, ?)`).run(cacheName, key, value, expiresAt);
|
|
1688
|
+
return c.json({ success: true });
|
|
1689
|
+
};
|
|
1690
|
+
const deleteHandler = async (c) => {
|
|
1691
|
+
const body = await c.req.json();
|
|
1692
|
+
const { cacheName, key } = body;
|
|
1693
|
+
db.prepare(`DELETE FROM cache_entries WHERE cache_name = ? AND key = ?`).run(cacheName, key);
|
|
1694
|
+
return c.json({ success: true });
|
|
1695
|
+
};
|
|
1696
|
+
return {
|
|
1697
|
+
getHandler,
|
|
1698
|
+
setHandler,
|
|
1699
|
+
deleteHandler
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
var init_cache_service = __esm({
|
|
1703
|
+
"../emulator/dist/services/cache-service.js"() {
|
|
1704
|
+
}
|
|
1705
|
+
});
|
|
1319
1706
|
function findDashboardDistPath() {
|
|
1320
1707
|
const possiblePaths = [
|
|
1321
1708
|
join(__dirname, "dashboard-dist"),
|
|
@@ -1343,9 +1730,175 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1343
1730
|
return c.json({
|
|
1344
1731
|
db: config.db,
|
|
1345
1732
|
queue: config.queue,
|
|
1346
|
-
|
|
1733
|
+
cache: config.cache,
|
|
1734
|
+
workflow: config.workflow,
|
|
1735
|
+
auth: config.auth
|
|
1347
1736
|
});
|
|
1348
1737
|
});
|
|
1738
|
+
if (config.auth) {
|
|
1739
|
+
app.get("/api/auth/tables", (c) => {
|
|
1740
|
+
try {
|
|
1741
|
+
const db = dbManager2.emulatorDb;
|
|
1742
|
+
const tables = db.prepare(`SELECT name FROM sqlite_master
|
|
1743
|
+
WHERE type='table' AND (name = 'auth_users' OR name = 'auth_sessions')
|
|
1744
|
+
ORDER BY name`).all();
|
|
1745
|
+
return c.json({ tables });
|
|
1746
|
+
} catch (err) {
|
|
1747
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
app.get("/api/auth/tables/:tableName", (c) => {
|
|
1751
|
+
const tableName = c.req.param("tableName");
|
|
1752
|
+
if (tableName !== "auth_users" && tableName !== "auth_sessions") {
|
|
1753
|
+
return c.json({ error: "Table not found" }, 404);
|
|
1754
|
+
}
|
|
1755
|
+
const limit = parseInt(c.req.query("limit") || "50", 10);
|
|
1756
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
1757
|
+
try {
|
|
1758
|
+
const db = dbManager2.emulatorDb;
|
|
1759
|
+
const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
1760
|
+
const columns = columnsResult.map((col) => col.name);
|
|
1761
|
+
const countResult = db.prepare(`SELECT COUNT(*) as count FROM "${tableName}"`).get();
|
|
1762
|
+
const total = countResult.count;
|
|
1763
|
+
let data;
|
|
1764
|
+
if (tableName === "auth_users") {
|
|
1765
|
+
data = db.prepare(`SELECT id, email, email_verified, created_at, updated_at, metadata FROM "${tableName}" LIMIT ? OFFSET ?`).all(limit, offset);
|
|
1766
|
+
} else {
|
|
1767
|
+
data = db.prepare(`SELECT * FROM "${tableName}" LIMIT ? OFFSET ?`).all(limit, offset);
|
|
1768
|
+
}
|
|
1769
|
+
const visibleColumns = tableName === "auth_users" ? columns.filter((c2) => c2 !== "password_hash") : columns;
|
|
1770
|
+
return c.json({ data, columns: visibleColumns, total });
|
|
1771
|
+
} catch (err) {
|
|
1772
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1773
|
+
}
|
|
1774
|
+
});
|
|
1775
|
+
app.get("/api/auth/schema", (c) => {
|
|
1776
|
+
try {
|
|
1777
|
+
const db = dbManager2.emulatorDb;
|
|
1778
|
+
const tables = ["auth_users", "auth_sessions"].map((tableName) => {
|
|
1779
|
+
const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
1780
|
+
const visibleColumns = tableName === "auth_users" ? columnsResult.filter((col) => col.name !== "password_hash") : columnsResult;
|
|
1781
|
+
return {
|
|
1782
|
+
name: tableName,
|
|
1783
|
+
columns: visibleColumns.map((col) => ({
|
|
1784
|
+
name: col.name,
|
|
1785
|
+
type: col.type,
|
|
1786
|
+
notNull: col.notnull === 1,
|
|
1787
|
+
primaryKey: col.pk === 1
|
|
1788
|
+
}))
|
|
1789
|
+
};
|
|
1790
|
+
});
|
|
1791
|
+
return c.json({ tables });
|
|
1792
|
+
} catch (err) {
|
|
1793
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
app.post("/api/auth/query", async (c) => {
|
|
1797
|
+
const body = await c.req.json();
|
|
1798
|
+
const { query } = body;
|
|
1799
|
+
if (!query) {
|
|
1800
|
+
return c.json({ error: "Query is required" }, 400);
|
|
1801
|
+
}
|
|
1802
|
+
const normalizedQuery = query.trim().toUpperCase();
|
|
1803
|
+
if (!normalizedQuery.startsWith("SELECT")) {
|
|
1804
|
+
return c.json({ error: "Only SELECT queries are allowed on auth tables" }, 400);
|
|
1805
|
+
}
|
|
1806
|
+
const allowedTables = ["auth_users", "auth_sessions"];
|
|
1807
|
+
const hasDisallowedTable = !allowedTables.some((table) => query.toLowerCase().includes(`from ${table}`) || query.toLowerCase().includes(`join ${table}`));
|
|
1808
|
+
if (hasDisallowedTable) {
|
|
1809
|
+
return c.json({
|
|
1810
|
+
error: "Query must reference auth tables (auth_users or auth_sessions)"
|
|
1811
|
+
}, 400);
|
|
1812
|
+
}
|
|
1813
|
+
try {
|
|
1814
|
+
const db = dbManager2.emulatorDb;
|
|
1815
|
+
const startTime = Date.now();
|
|
1816
|
+
const stmt = db.prepare(query);
|
|
1817
|
+
const results = stmt.all();
|
|
1818
|
+
const sanitizedResults = results.map((row) => {
|
|
1819
|
+
const { password_hash: _, ...rest } = row;
|
|
1820
|
+
return rest;
|
|
1821
|
+
});
|
|
1822
|
+
const duration = Date.now() - startTime;
|
|
1823
|
+
return c.json({
|
|
1824
|
+
results: sanitizedResults,
|
|
1825
|
+
success: true,
|
|
1826
|
+
meta: {
|
|
1827
|
+
duration,
|
|
1828
|
+
rows_read: results.length,
|
|
1829
|
+
rows_written: 0
|
|
1830
|
+
}
|
|
1831
|
+
});
|
|
1832
|
+
} catch (err) {
|
|
1833
|
+
return c.json({
|
|
1834
|
+
results: [],
|
|
1835
|
+
success: false,
|
|
1836
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1837
|
+
meta: { duration: 0, rows_read: 0, rows_written: 0 }
|
|
1838
|
+
}, 400);
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
app.get("/api/auth/settings", (c) => {
|
|
1842
|
+
try {
|
|
1843
|
+
const db = dbManager2.emulatorDb;
|
|
1844
|
+
const settings = db.prepare("SELECT * FROM auth_settings WHERE id = 1").get();
|
|
1845
|
+
if (!settings) {
|
|
1846
|
+
return c.json({
|
|
1847
|
+
sessionTokenExpiry: 604800,
|
|
1848
|
+
allowSignups: true,
|
|
1849
|
+
requireEmailVerification: false,
|
|
1850
|
+
requireName: false
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
return c.json({
|
|
1854
|
+
sessionTokenExpiry: settings.session_token_expiry,
|
|
1855
|
+
allowSignups: settings.allow_signups === 1,
|
|
1856
|
+
requireEmailVerification: settings.require_email_verification === 1,
|
|
1857
|
+
requireName: settings.require_name === 1
|
|
1858
|
+
});
|
|
1859
|
+
} catch (err) {
|
|
1860
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
app.patch("/api/auth/settings", async (c) => {
|
|
1864
|
+
try {
|
|
1865
|
+
const body = await c.req.json();
|
|
1866
|
+
const db = dbManager2.emulatorDb;
|
|
1867
|
+
const updates = [];
|
|
1868
|
+
const values = [];
|
|
1869
|
+
if (body.sessionTokenExpiry !== void 0) {
|
|
1870
|
+
updates.push("session_token_expiry = ?");
|
|
1871
|
+
values.push(body.sessionTokenExpiry);
|
|
1872
|
+
}
|
|
1873
|
+
if (body.allowSignups !== void 0) {
|
|
1874
|
+
updates.push("allow_signups = ?");
|
|
1875
|
+
values.push(body.allowSignups ? 1 : 0);
|
|
1876
|
+
}
|
|
1877
|
+
if (body.requireEmailVerification !== void 0) {
|
|
1878
|
+
updates.push("require_email_verification = ?");
|
|
1879
|
+
values.push(body.requireEmailVerification ? 1 : 0);
|
|
1880
|
+
}
|
|
1881
|
+
if (body.requireName !== void 0) {
|
|
1882
|
+
updates.push("require_name = ?");
|
|
1883
|
+
values.push(body.requireName ? 1 : 0);
|
|
1884
|
+
}
|
|
1885
|
+
if (updates.length > 0) {
|
|
1886
|
+
updates.push("updated_at = strftime('%s', 'now')");
|
|
1887
|
+
const sql = `UPDATE auth_settings SET ${updates.join(", ")} WHERE id = 1`;
|
|
1888
|
+
db.prepare(sql).run(...values);
|
|
1889
|
+
}
|
|
1890
|
+
const settings = db.prepare("SELECT * FROM auth_settings WHERE id = 1").get();
|
|
1891
|
+
return c.json({
|
|
1892
|
+
sessionTokenExpiry: settings.session_token_expiry,
|
|
1893
|
+
allowSignups: settings.allow_signups === 1,
|
|
1894
|
+
requireEmailVerification: settings.require_email_verification === 1,
|
|
1895
|
+
requireName: settings.require_name === 1
|
|
1896
|
+
});
|
|
1897
|
+
} catch (err) {
|
|
1898
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1349
1902
|
app.post("/api/db/:binding/query", async (c) => {
|
|
1350
1903
|
const binding = c.req.param("binding");
|
|
1351
1904
|
const resourceName = getDbResourceName(binding);
|
|
@@ -1518,6 +2071,39 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1518
2071
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1519
2072
|
}
|
|
1520
2073
|
});
|
|
2074
|
+
app.get("/api/cache/:binding/entries", (c) => {
|
|
2075
|
+
const binding = c.req.param("binding");
|
|
2076
|
+
const cacheName = config.cache?.[binding];
|
|
2077
|
+
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
2078
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
2079
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2080
|
+
if (!cacheName) {
|
|
2081
|
+
return c.json({ error: "Cache binding not found" }, 404);
|
|
2082
|
+
}
|
|
2083
|
+
try {
|
|
2084
|
+
const db = dbManager2.emulatorDb;
|
|
2085
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM cache_entries
|
|
2086
|
+
WHERE cache_name = ? AND expires_at > ?`).get(cacheName, now).count;
|
|
2087
|
+
const entries = db.prepare(`SELECT key, value, expires_at
|
|
2088
|
+
FROM cache_entries
|
|
2089
|
+
WHERE cache_name = ? AND expires_at > ?
|
|
2090
|
+
ORDER BY key ASC
|
|
2091
|
+
LIMIT ? OFFSET ?`).all(cacheName, now, limit, offset);
|
|
2092
|
+
return c.json({
|
|
2093
|
+
entries: entries.map((e) => ({
|
|
2094
|
+
key: e.key,
|
|
2095
|
+
value: e.value,
|
|
2096
|
+
ttlSeconds: Math.max(0, e.expires_at - now),
|
|
2097
|
+
expiresAt: new Date(e.expires_at * 1e3).toISOString()
|
|
2098
|
+
})),
|
|
2099
|
+
total,
|
|
2100
|
+
limit,
|
|
2101
|
+
offset
|
|
2102
|
+
});
|
|
2103
|
+
} catch (err) {
|
|
2104
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
1521
2107
|
app.get("/api/workflow/:binding/executions", (c) => {
|
|
1522
2108
|
const binding = c.req.param("binding");
|
|
1523
2109
|
const workflowConfig = config.workflow?.[binding];
|
|
@@ -1568,7 +2154,7 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1568
2154
|
}
|
|
1569
2155
|
try {
|
|
1570
2156
|
const db = dbManager2.emulatorDb;
|
|
1571
|
-
const execution = db.prepare(`SELECT id, workflow_name, status, error, started_at, completed_at, created_at
|
|
2157
|
+
const execution = db.prepare(`SELECT id, workflow_name, status, input, output, error, started_at, completed_at, created_at
|
|
1572
2158
|
FROM workflow_executions
|
|
1573
2159
|
WHERE id = ?`).get(executionId);
|
|
1574
2160
|
if (!execution) {
|
|
@@ -1582,6 +2168,8 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1582
2168
|
execution: {
|
|
1583
2169
|
id: execution.id,
|
|
1584
2170
|
status: execution.status.toUpperCase(),
|
|
2171
|
+
input: execution.input ? JSON.parse(execution.input) : null,
|
|
2172
|
+
output: execution.output ? JSON.parse(execution.output) : null,
|
|
1585
2173
|
startedAt: execution.started_at ? new Date(execution.started_at * 1e3).toISOString() : null,
|
|
1586
2174
|
completedAt: execution.completed_at ? new Date(execution.completed_at * 1e3).toISOString() : null,
|
|
1587
2175
|
durationMs: execution.started_at && execution.completed_at ? (execution.completed_at - execution.started_at) * 1e3 : null,
|
|
@@ -1830,8 +2418,10 @@ function createQueueHandlers(db) {
|
|
|
1830
2418
|
try {
|
|
1831
2419
|
const body = await c.req.json();
|
|
1832
2420
|
const { messageId, deliveryId } = body;
|
|
1833
|
-
const
|
|
1834
|
-
|
|
2421
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2422
|
+
const result = db.prepare(`UPDATE queue_messages
|
|
2423
|
+
SET status = 'acknowledged', updated_at = ?
|
|
2424
|
+
WHERE id = ? AND delivery_id = ?`).run(now, messageId, deliveryId);
|
|
1835
2425
|
if (result.changes === 0) {
|
|
1836
2426
|
return c.json({ success: false, error: "Message not found or already processed" }, 404);
|
|
1837
2427
|
}
|
|
@@ -1906,7 +2496,7 @@ function createQueueProcessor(db, queueBindings, workerUrl) {
|
|
|
1906
2496
|
body: row.payload
|
|
1907
2497
|
});
|
|
1908
2498
|
if (response.ok) {
|
|
1909
|
-
db.prepare(`
|
|
2499
|
+
db.prepare(`UPDATE queue_messages SET status = 'acknowledged', updated_at = ? WHERE id = ?`).run(now, row.id);
|
|
1910
2500
|
} else {
|
|
1911
2501
|
db.prepare(`UPDATE queue_messages
|
|
1912
2502
|
SET status = 'pending', delivery_id = NULL, visible_at = ?, updated_at = ?
|
|
@@ -2143,6 +2733,19 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
2143
2733
|
app.post("/workflow/complete", workflowHandlers.completeHandler);
|
|
2144
2734
|
app.post("/workflow/fail", workflowHandlers.failHandler);
|
|
2145
2735
|
}
|
|
2736
|
+
if (config.cache) {
|
|
2737
|
+
const cacheHandlers = createCacheHandlers(dbManager2.emulatorDb);
|
|
2738
|
+
app.post("/cache/get", cacheHandlers.getHandler);
|
|
2739
|
+
app.post("/cache/set", cacheHandlers.setHandler);
|
|
2740
|
+
app.post("/cache/delete", cacheHandlers.deleteHandler);
|
|
2741
|
+
}
|
|
2742
|
+
if (config.auth) {
|
|
2743
|
+
const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
|
|
2744
|
+
app.post("/auth/signup", authHandlers.signupHandler);
|
|
2745
|
+
app.post("/auth/signin", authHandlers.signinHandler);
|
|
2746
|
+
app.get("/auth/me", authHandlers.meHandler);
|
|
2747
|
+
app.post("/auth/signout", authHandlers.signoutHandler);
|
|
2748
|
+
}
|
|
2146
2749
|
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
2147
2750
|
if (options.dashboardEnabled !== false) {
|
|
2148
2751
|
createDashboardRoutes(app, dbManager2, config);
|
|
@@ -2165,6 +2768,8 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
2165
2768
|
var DEFAULT_MOCK_SERVER_PORT;
|
|
2166
2769
|
var init_mock_server = __esm({
|
|
2167
2770
|
"../emulator/dist/services/mock-server.js"() {
|
|
2771
|
+
init_auth_service();
|
|
2772
|
+
init_cache_service();
|
|
2168
2773
|
init_dashboard_routes();
|
|
2169
2774
|
init_db_service();
|
|
2170
2775
|
init_queue_service();
|
|
@@ -2281,6 +2886,58 @@ CREATE TABLE IF NOT EXISTS workflow_steps (
|
|
|
2281
2886
|
|
|
2282
2887
|
CREATE INDEX IF NOT EXISTS idx_workflow_steps_execution
|
|
2283
2888
|
ON workflow_steps(execution_id, step_index);
|
|
2889
|
+
|
|
2890
|
+
-- Auth users table
|
|
2891
|
+
CREATE TABLE IF NOT EXISTS auth_users (
|
|
2892
|
+
id TEXT PRIMARY KEY,
|
|
2893
|
+
email TEXT UNIQUE NOT NULL,
|
|
2894
|
+
email_verified INTEGER NOT NULL DEFAULT 0,
|
|
2895
|
+
password_hash TEXT NOT NULL,
|
|
2896
|
+
created_at TEXT NOT NULL,
|
|
2897
|
+
updated_at TEXT NOT NULL,
|
|
2898
|
+
metadata TEXT
|
|
2899
|
+
);
|
|
2900
|
+
|
|
2901
|
+
CREATE INDEX IF NOT EXISTS idx_auth_users_email
|
|
2902
|
+
ON auth_users(email);
|
|
2903
|
+
|
|
2904
|
+
-- Auth sessions table
|
|
2905
|
+
CREATE TABLE IF NOT EXISTS auth_sessions (
|
|
2906
|
+
id TEXT PRIMARY KEY,
|
|
2907
|
+
user_id TEXT NOT NULL,
|
|
2908
|
+
token_hash TEXT UNIQUE NOT NULL,
|
|
2909
|
+
expires_at TEXT NOT NULL,
|
|
2910
|
+
created_at TEXT NOT NULL,
|
|
2911
|
+
revoked INTEGER NOT NULL DEFAULT 0,
|
|
2912
|
+
FOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE
|
|
2913
|
+
);
|
|
2914
|
+
|
|
2915
|
+
CREATE INDEX IF NOT EXISTS idx_auth_sessions_user
|
|
2916
|
+
ON auth_sessions(user_id);
|
|
2917
|
+
CREATE INDEX IF NOT EXISTS idx_auth_sessions_hash
|
|
2918
|
+
ON auth_sessions(token_hash);
|
|
2919
|
+
|
|
2920
|
+
-- Auth settings table
|
|
2921
|
+
CREATE TABLE IF NOT EXISTS auth_settings (
|
|
2922
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
2923
|
+
session_token_expiry INTEGER NOT NULL DEFAULT 604800,
|
|
2924
|
+
allow_signups INTEGER NOT NULL DEFAULT 1,
|
|
2925
|
+
require_email_verification INTEGER NOT NULL DEFAULT 0,
|
|
2926
|
+
require_name INTEGER NOT NULL DEFAULT 0,
|
|
2927
|
+
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
2928
|
+
);
|
|
2929
|
+
|
|
2930
|
+
-- Insert default settings if not exists
|
|
2931
|
+
INSERT OR IGNORE INTO auth_settings (id) VALUES (1);
|
|
2932
|
+
|
|
2933
|
+
-- Cache entries table
|
|
2934
|
+
CREATE TABLE IF NOT EXISTS cache_entries (
|
|
2935
|
+
cache_name TEXT NOT NULL,
|
|
2936
|
+
key TEXT NOT NULL,
|
|
2937
|
+
value TEXT NOT NULL,
|
|
2938
|
+
expires_at INTEGER NOT NULL,
|
|
2939
|
+
PRIMARY KEY (cache_name, key)
|
|
2940
|
+
);
|
|
2284
2941
|
`;
|
|
2285
2942
|
}
|
|
2286
2943
|
});
|
|
@@ -2381,6 +3038,9 @@ var init_emulator = __esm({
|
|
|
2381
3038
|
if (this.config.queue) {
|
|
2382
3039
|
log(` Queue bindings: ${Object.keys(this.config.queue).join(", ")}`);
|
|
2383
3040
|
}
|
|
3041
|
+
if (this.config.cache) {
|
|
3042
|
+
log(` Cache bindings: ${Object.keys(this.config.cache).join(", ")}`);
|
|
3043
|
+
}
|
|
2384
3044
|
if (this.config.workflow) {
|
|
2385
3045
|
log(` Workflow bindings: ${Object.keys(this.config.workflow).join(", ")}`);
|
|
2386
3046
|
}
|
|
@@ -2635,6 +3295,38 @@ function createDevD1(databaseId, apiUrl) {
|
|
|
2635
3295
|
}
|
|
2636
3296
|
};
|
|
2637
3297
|
}
|
|
3298
|
+
function createDevPloyAuth(apiUrl) {
|
|
3299
|
+
return {
|
|
3300
|
+
async getUser(token) {
|
|
3301
|
+
try {
|
|
3302
|
+
const response = await fetch(`${apiUrl}/auth/me`, {
|
|
3303
|
+
headers: {
|
|
3304
|
+
Authorization: `Bearer ${token}`
|
|
3305
|
+
}
|
|
3306
|
+
});
|
|
3307
|
+
if (!response.ok) {
|
|
3308
|
+
return null;
|
|
3309
|
+
}
|
|
3310
|
+
const data = await response.json();
|
|
3311
|
+
return data.user;
|
|
3312
|
+
} catch {
|
|
3313
|
+
return null;
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
3316
|
+
async verifyToken(token) {
|
|
3317
|
+
try {
|
|
3318
|
+
const response = await fetch(`${apiUrl}/auth/me`, {
|
|
3319
|
+
headers: {
|
|
3320
|
+
Authorization: `Bearer ${token}`
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
return response.ok;
|
|
3324
|
+
} catch {
|
|
3325
|
+
return false;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
};
|
|
3329
|
+
}
|
|
2638
3330
|
async function initPloyForDev(config) {
|
|
2639
3331
|
if (process.env.NODE_ENV !== "development") {
|
|
2640
3332
|
return;
|
|
@@ -2643,6 +3335,56 @@ async function initPloyForDev(config) {
|
|
|
2643
3335
|
return;
|
|
2644
3336
|
}
|
|
2645
3337
|
globalThis.__PLOY_DEV_INITIALIZED__ = true;
|
|
3338
|
+
const cliMockServerUrl = process.env.PLOY_MOCK_SERVER_URL;
|
|
3339
|
+
if (cliMockServerUrl) {
|
|
3340
|
+
const configPath2 = config?.configPath || "./ploy.yaml";
|
|
3341
|
+
const projectDir2 = process.cwd();
|
|
3342
|
+
let ployConfig2;
|
|
3343
|
+
try {
|
|
3344
|
+
ployConfig2 = readPloyConfig2(projectDir2, configPath2);
|
|
3345
|
+
} catch {
|
|
3346
|
+
if (config?.bindings?.db) {
|
|
3347
|
+
ployConfig2 = { db: config.bindings.db };
|
|
3348
|
+
} else {
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
if (config?.bindings?.db) {
|
|
3353
|
+
ployConfig2 = { ...ployConfig2, db: config.bindings.db };
|
|
3354
|
+
}
|
|
3355
|
+
const hasDbBindings2 = ployConfig2.db && Object.keys(ployConfig2.db).length > 0;
|
|
3356
|
+
const hasAuthConfig2 = !!ployConfig2.auth;
|
|
3357
|
+
if (!hasDbBindings2 && !hasAuthConfig2) {
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
const env2 = {};
|
|
3361
|
+
if (hasDbBindings2 && ployConfig2.db) {
|
|
3362
|
+
for (const [bindingName, databaseId] of Object.entries(ployConfig2.db)) {
|
|
3363
|
+
env2[bindingName] = createDevD1(databaseId, cliMockServerUrl);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
if (hasAuthConfig2) {
|
|
3367
|
+
env2.PLOY_AUTH = createDevPloyAuth(cliMockServerUrl);
|
|
3368
|
+
}
|
|
3369
|
+
const context2 = { env: env2, cf: void 0, ctx: void 0 };
|
|
3370
|
+
globalThis.__PLOY_DEV_CONTEXT__ = context2;
|
|
3371
|
+
Object.defineProperty(globalThis, PLOY_CONTEXT_SYMBOL, {
|
|
3372
|
+
get() {
|
|
3373
|
+
return context2;
|
|
3374
|
+
},
|
|
3375
|
+
configurable: true
|
|
3376
|
+
});
|
|
3377
|
+
const bindingNames2 = Object.keys(env2);
|
|
3378
|
+
const features2 = [];
|
|
3379
|
+
if (bindingNames2.length > 0) {
|
|
3380
|
+
features2.push(`bindings: ${bindingNames2.join(", ")}`);
|
|
3381
|
+
}
|
|
3382
|
+
if (hasAuthConfig2) {
|
|
3383
|
+
features2.push("auth");
|
|
3384
|
+
}
|
|
3385
|
+
console.log(`[Ploy] Using CLI mock server at ${cliMockServerUrl} (${features2.join(", ")})`);
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
2646
3388
|
const configPath = config?.configPath || "./ploy.yaml";
|
|
2647
3389
|
const projectDir = process.cwd();
|
|
2648
3390
|
let ployConfig;
|
|
@@ -2658,7 +3400,9 @@ async function initPloyForDev(config) {
|
|
|
2658
3400
|
if (config?.bindings?.db) {
|
|
2659
3401
|
ployConfig = { ...ployConfig, db: config.bindings.db };
|
|
2660
3402
|
}
|
|
2661
|
-
|
|
3403
|
+
const hasDbBindings = ployConfig.db && Object.keys(ployConfig.db).length > 0;
|
|
3404
|
+
const hasAuthConfig = !!ployConfig.auth;
|
|
3405
|
+
if (!hasDbBindings && !hasAuthConfig) {
|
|
2662
3406
|
return;
|
|
2663
3407
|
}
|
|
2664
3408
|
ensureDataDir(projectDir);
|
|
@@ -2666,8 +3410,14 @@ async function initPloyForDev(config) {
|
|
|
2666
3410
|
mockServer = await startMockServer(dbManager, ployConfig, {});
|
|
2667
3411
|
const apiUrl = `http://localhost:${mockServer.port}`;
|
|
2668
3412
|
const env = {};
|
|
2669
|
-
|
|
2670
|
-
|
|
3413
|
+
if (hasDbBindings && ployConfig.db) {
|
|
3414
|
+
for (const [bindingName, databaseId] of Object.entries(ployConfig.db)) {
|
|
3415
|
+
env[bindingName] = createDevD1(databaseId, apiUrl);
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
if (hasAuthConfig) {
|
|
3419
|
+
env.PLOY_AUTH = createDevPloyAuth(apiUrl);
|
|
3420
|
+
process.env.NEXT_PUBLIC_PLOY_AUTH_URL = `${apiUrl}/auth`;
|
|
2671
3421
|
}
|
|
2672
3422
|
const context = {
|
|
2673
3423
|
env,
|
|
@@ -2682,7 +3432,14 @@ async function initPloyForDev(config) {
|
|
|
2682
3432
|
configurable: true
|
|
2683
3433
|
});
|
|
2684
3434
|
const bindingNames = Object.keys(env);
|
|
2685
|
-
|
|
3435
|
+
const features = [];
|
|
3436
|
+
if (bindingNames.length > 0) {
|
|
3437
|
+
features.push(`bindings: ${bindingNames.join(", ")}`);
|
|
3438
|
+
}
|
|
3439
|
+
if (hasAuthConfig) {
|
|
3440
|
+
features.push("auth");
|
|
3441
|
+
}
|
|
3442
|
+
console.log(`[Ploy] Development context initialized with ${features.join(", ")}`);
|
|
2686
3443
|
console.log(`[Ploy] Mock server running at ${apiUrl}`);
|
|
2687
3444
|
const cleanup = async () => {
|
|
2688
3445
|
if (mockServer) {
|
|
@@ -4268,6 +5025,12 @@ function generateEnvType(config) {
|
|
|
4268
5025
|
properties.push(` ${bindingName}: QueueBinding;`);
|
|
4269
5026
|
}
|
|
4270
5027
|
}
|
|
5028
|
+
if (config.cache) {
|
|
5029
|
+
imports.push("CacheBinding");
|
|
5030
|
+
for (const bindingName of Object.keys(config.cache)) {
|
|
5031
|
+
properties.push(` ${bindingName}: CacheBinding;`);
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
4271
5034
|
if (config.workflow) {
|
|
4272
5035
|
imports.push("WorkflowBinding");
|
|
4273
5036
|
for (const bindingName of Object.keys(config.workflow)) {
|
|
@@ -4315,7 +5078,7 @@ async function typesCommand(options = {}) {
|
|
|
4315
5078
|
console.error("Error: ploy.yaml not found in current directory");
|
|
4316
5079
|
process.exit(1);
|
|
4317
5080
|
}
|
|
4318
|
-
const hasBindings2 = config.ai || config.db || config.queue || config.workflow;
|
|
5081
|
+
const hasBindings2 = config.ai || config.db || config.queue || config.cache || config.workflow;
|
|
4319
5082
|
if (!hasBindings2) {
|
|
4320
5083
|
console.log("No bindings found in ploy.yaml. Generating empty Env.");
|
|
4321
5084
|
}
|
|
@@ -4476,7 +5239,9 @@ async function startNextJsDev(options) {
|
|
|
4476
5239
|
env: {
|
|
4477
5240
|
...process.env,
|
|
4478
5241
|
// Set environment variable so initPloyForDev knows the mock server URL
|
|
4479
|
-
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}
|
|
5242
|
+
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`,
|
|
5243
|
+
// Set PORT so initPloyForDev can construct the correct handler URL
|
|
5244
|
+
PORT: String(nextPort)
|
|
4480
5245
|
}
|
|
4481
5246
|
});
|
|
4482
5247
|
} else {
|
|
@@ -4486,7 +5251,8 @@ async function startNextJsDev(options) {
|
|
|
4486
5251
|
shell: true,
|
|
4487
5252
|
env: {
|
|
4488
5253
|
...process.env,
|
|
4489
|
-
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}
|
|
5254
|
+
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`,
|
|
5255
|
+
PORT: String(nextPort)
|
|
4490
5256
|
}
|
|
4491
5257
|
});
|
|
4492
5258
|
}
|