@codemem/server 0.29.3 → 0.30.0-alpha.1
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/index.js +464 -51
- package/dist/index.js.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +75 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, MemoryStore, VERSION, VERSION as VERSION$1, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listMaintenanceJobs, listObserverProviderOptions, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, parseStrictInteger, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas,
|
|
3
|
+
import { CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromHook, canonicalMemoryKind, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isSummaryLikeMemory, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, verifySignature, writeCodememConfigFile } from "@codemem/core";
|
|
4
4
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { createMiddleware } from "hono/factory";
|
|
@@ -1526,6 +1526,51 @@ function coordinatorAdminStatusPayload(config = readCoordinatorSyncConfig()) {
|
|
|
1526
1526
|
has_groups: hasGroups
|
|
1527
1527
|
};
|
|
1528
1528
|
}
|
|
1529
|
+
function coordinatorAdminUnavailable(status) {
|
|
1530
|
+
if (status.readiness === "not_configured") return {
|
|
1531
|
+
body: {
|
|
1532
|
+
error: "coordinator_not_configured",
|
|
1533
|
+
status
|
|
1534
|
+
},
|
|
1535
|
+
httpStatus: 400
|
|
1536
|
+
};
|
|
1537
|
+
if (!status.has_admin_secret) return {
|
|
1538
|
+
body: {
|
|
1539
|
+
error: "coordinator_admin_secret_missing",
|
|
1540
|
+
status
|
|
1541
|
+
},
|
|
1542
|
+
httpStatus: 400
|
|
1543
|
+
};
|
|
1544
|
+
return null;
|
|
1545
|
+
}
|
|
1546
|
+
async function parseViewerJsonBody(c, options = {}) {
|
|
1547
|
+
const raw = await c.req.text();
|
|
1548
|
+
if (!raw.trim()) return options.allowEmpty ? {} : null;
|
|
1549
|
+
try {
|
|
1550
|
+
const parsed = JSON.parse(raw);
|
|
1551
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
1552
|
+
return parsed;
|
|
1553
|
+
} catch {
|
|
1554
|
+
return null;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
function optionalViewerString(body, key) {
|
|
1558
|
+
const value = body[key];
|
|
1559
|
+
if (value == null) return null;
|
|
1560
|
+
return String(value).trim() || null;
|
|
1561
|
+
}
|
|
1562
|
+
function optionalViewerNumber(body, key) {
|
|
1563
|
+
const value = body[key];
|
|
1564
|
+
if (value == null || value === "") return null;
|
|
1565
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
1566
|
+
return Number.isFinite(number) ? Math.trunc(number) : NaN;
|
|
1567
|
+
}
|
|
1568
|
+
function coordinatorAdminMutationStatus(message) {
|
|
1569
|
+
if (message.includes("scope_not_found") || message.includes("membership_not_found") || message.includes("group_not_found") || message.includes("Group not found") || message.includes("Scope not found") || message.includes("device_not_enrolled_for_scope_group") || message.includes("device must be enrolled")) return 404;
|
|
1570
|
+
if (message.includes("not active") || message.includes("scope_not_active") || message.includes("group_archived") || message.includes("Group is archived")) return 409;
|
|
1571
|
+
if (message.includes("Remote coordinator request failed (5") || message.includes("fetch failed") || message.includes("network") || message.includes("timed out")) return 502;
|
|
1572
|
+
return 400;
|
|
1573
|
+
}
|
|
1529
1574
|
function pairingAdvertiseAddresses(config = readCoordinatorSyncConfig()) {
|
|
1530
1575
|
const advertise = String(config.syncAdvertise || "auto").trim().toLowerCase();
|
|
1531
1576
|
if (advertise && advertise !== "auto" && advertise !== "default") return mergeAddresses([], String(config.syncAdvertise || "").split(",").map((item) => item.trim()).filter(Boolean));
|
|
@@ -1899,20 +1944,6 @@ function claimLegacyDeviceAsSelf(store, originDeviceId) {
|
|
|
1899
1944
|
)`).run(store.actorId, store.actorDisplayName, personalWorkspaceId, deviceId, store.actorId, LEGACY_SYNC_ACTOR_DISPLAY_NAME, LEGACY_SHARED_WORKSPACE_ID);
|
|
1900
1945
|
return Number(result.changes ?? 0);
|
|
1901
1946
|
}
|
|
1902
|
-
function peerClaimedLocalActor(store, peerDeviceId) {
|
|
1903
|
-
const row = store.db.prepare("SELECT claimed_local_actor FROM sync_peers WHERE peer_device_id = ? LIMIT 1").get(peerDeviceId);
|
|
1904
|
-
return Boolean(row?.claimed_local_actor);
|
|
1905
|
-
}
|
|
1906
|
-
function parseOpPayload(op) {
|
|
1907
|
-
if (!op.payload_json || !String(op.payload_json).trim()) return null;
|
|
1908
|
-
try {
|
|
1909
|
-
const parsed = JSON.parse(op.payload_json);
|
|
1910
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
1911
|
-
return parsed;
|
|
1912
|
-
} catch {
|
|
1913
|
-
return null;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
1947
|
function redactCoordinatorStatus(coordinator, showDiag) {
|
|
1917
1948
|
if (showDiag) return coordinator;
|
|
1918
1949
|
const discovered = Array.isArray(coordinator.discovered_devices) ? coordinator.discovered_devices.map((item) => {
|
|
@@ -2076,30 +2107,29 @@ async function acceptDiscoveredPeer(store, input) {
|
|
|
2076
2107
|
};
|
|
2077
2108
|
})();
|
|
2078
2109
|
}
|
|
2079
|
-
function filterOpsForPeer(store, peerDeviceId, ops) {
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
for (const op of ops) {
|
|
2085
|
-
if (op.entity_type !== "memory_item") {
|
|
2086
|
-
allowed.push(op);
|
|
2087
|
-
continue;
|
|
2088
|
-
}
|
|
2089
|
-
const payload = parseOpPayload(op);
|
|
2090
|
-
if (!allowPrivate && !syncVisibilityAllowed(payload)) {
|
|
2091
|
-
skipped++;
|
|
2092
|
-
continue;
|
|
2093
|
-
}
|
|
2094
|
-
if (!syncProjectAllowedByFilters(payload && typeof payload.project === "string" ? payload.project : null, filters)) {
|
|
2095
|
-
skipped++;
|
|
2096
|
-
continue;
|
|
2097
|
-
}
|
|
2098
|
-
allowed.push(op);
|
|
2099
|
-
}
|
|
2110
|
+
function filterOpsForPeer(store, peerDeviceId, localDeviceId, ops, options = {}) {
|
|
2111
|
+
const [allowed, , skipped] = filterReplicationOpsForSyncWithStatus(store.db, ops, peerDeviceId, {
|
|
2112
|
+
localDeviceId,
|
|
2113
|
+
applyScopeFilter: options.applyScopeFilter
|
|
2114
|
+
});
|
|
2100
2115
|
return {
|
|
2101
2116
|
allowed,
|
|
2102
|
-
skipped
|
|
2117
|
+
skipped: skipped?.skipped_count ?? 0
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
function claimedLocalActorScopeStatus(store, row) {
|
|
2121
|
+
if (!row.claimed_local_actor) return null;
|
|
2122
|
+
const peerDeviceId = String(row.peer_device_id ?? "").trim();
|
|
2123
|
+
const actorId = String(row.actor_id ?? store.actorId).trim() || store.actorId;
|
|
2124
|
+
const grant = personalScopeGrantStatusForPeer(store.db, {
|
|
2125
|
+
peerDeviceId,
|
|
2126
|
+
actorId
|
|
2127
|
+
});
|
|
2128
|
+
return {
|
|
2129
|
+
scope_id: grant.scope_id,
|
|
2130
|
+
authorized: grant.authorized,
|
|
2131
|
+
state: grant.state,
|
|
2132
|
+
action_required: !grant.authorized
|
|
2103
2133
|
};
|
|
2104
2134
|
}
|
|
2105
2135
|
/**
|
|
@@ -2107,12 +2137,13 @@ function filterOpsForPeer(store, peerDeviceId, ops) {
|
|
|
2107
2137
|
* When showDiag is false, sensitive fields (fingerprint, last_error, addresses)
|
|
2108
2138
|
* are redacted.
|
|
2109
2139
|
*/
|
|
2110
|
-
function mapPeerRow(store, row, showDiag, recentOpsByPeer) {
|
|
2140
|
+
function mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer) {
|
|
2111
2141
|
const peerId = String(row.peer_device_id ?? "");
|
|
2112
2142
|
const recentOps = recentOpsByPeer?.get(peerId) ?? {
|
|
2113
2143
|
in: 0,
|
|
2114
2144
|
out: 0
|
|
2115
2145
|
};
|
|
2146
|
+
const scopeRejections = scopeRejectionsByPeer?.get(peerId);
|
|
2116
2147
|
return {
|
|
2117
2148
|
peer_device_id: row.peer_device_id,
|
|
2118
2149
|
name: row.name,
|
|
@@ -2124,6 +2155,7 @@ function mapPeerRow(store, row, showDiag, recentOpsByPeer) {
|
|
|
2124
2155
|
last_error: showDiag ? row.last_error : null,
|
|
2125
2156
|
has_error: Boolean(row.last_error),
|
|
2126
2157
|
claimed_local_actor: Boolean(row.claimed_local_actor),
|
|
2158
|
+
claimed_local_actor_scope: showDiag ? claimedLocalActorScopeStatus(store, row) : null,
|
|
2127
2159
|
actor_id: row.actor_id ?? null,
|
|
2128
2160
|
actor_display_name: row.actor_display_name ?? null,
|
|
2129
2161
|
project_scope: { ...currentProjectScope(row, readPeerProjectFilters(store, String(row.peer_device_id ?? ""))) },
|
|
@@ -2131,10 +2163,27 @@ function mapPeerRow(store, row, showDiag, recentOpsByPeer) {
|
|
|
2131
2163
|
in: recentOps.in,
|
|
2132
2164
|
out: recentOps.out
|
|
2133
2165
|
},
|
|
2166
|
+
scope_rejections: {
|
|
2167
|
+
total: scopeRejections?.total ?? 0,
|
|
2168
|
+
by_reason: scopeRejections?.by_reason ?? {},
|
|
2169
|
+
last_at: scopeRejections?.last_at ?? null
|
|
2170
|
+
},
|
|
2134
2171
|
discovered_via_coordinator_id: typeof row.discovered_via_coordinator_id === "string" ? row.discovered_via_coordinator_id : null,
|
|
2135
2172
|
discovered_via_group_id: typeof row.discovered_via_group_id === "string" ? row.discovered_via_group_id : null
|
|
2136
2173
|
};
|
|
2137
2174
|
}
|
|
2175
|
+
var SYNC_SCOPE_REJECTION_WINDOW_SECONDS = 1440 * 60;
|
|
2176
|
+
function recentScopeRejectionsByPeer(store) {
|
|
2177
|
+
const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_SCOPE_REJECTION_WINDOW_SECONDS * 1e3)).toISOString();
|
|
2178
|
+
const summaries = summarizeInboundScopeRejections(store.db, { sinceIso: cutoff });
|
|
2179
|
+
const map = /* @__PURE__ */ new Map();
|
|
2180
|
+
for (const summary of summaries) {
|
|
2181
|
+
const id = String(summary.peer_device_id ?? "").trim();
|
|
2182
|
+
if (!id) continue;
|
|
2183
|
+
map.set(id, summary);
|
|
2184
|
+
}
|
|
2185
|
+
return map;
|
|
2186
|
+
}
|
|
2138
2187
|
var SYNC_DIRECTION_WINDOW_SECONDS = 1440 * 60;
|
|
2139
2188
|
function recentPeerOps(store) {
|
|
2140
2189
|
const cutoff = (/* @__PURE__ */ new Date(Date.now() - SYNC_DIRECTION_WINDOW_SECONDS * 1e3)).toISOString();
|
|
@@ -2265,7 +2314,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2265
2314
|
device_id: deviceId,
|
|
2266
2315
|
protocol_version: SYNC_PROTOCOL_VERSION,
|
|
2267
2316
|
fingerprint,
|
|
2268
|
-
sync_reset: syncReset
|
|
2317
|
+
sync_reset: addSyncScopeToBoundary(syncReset, null),
|
|
2318
|
+
sync_capability: LOCAL_SYNC_CAPABILITY
|
|
2269
2319
|
});
|
|
2270
2320
|
} catch {
|
|
2271
2321
|
return c.json({ error: "internal_error" }, 500);
|
|
@@ -2280,6 +2330,9 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2280
2330
|
if (limited) return limited;
|
|
2281
2331
|
const peerDeviceId = auth.deviceId;
|
|
2282
2332
|
try {
|
|
2333
|
+
const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
|
|
2334
|
+
const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0);
|
|
2335
|
+
if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY), 409);
|
|
2283
2336
|
const since = c.req.query("since") ?? null;
|
|
2284
2337
|
const rawLimit = Number.parseInt(c.req.query("limit") ?? "200", 10);
|
|
2285
2338
|
const rawGeneration = c.req.query("generation");
|
|
@@ -2298,12 +2351,15 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2298
2351
|
});
|
|
2299
2352
|
if (result.reset_required) return c.json({
|
|
2300
2353
|
error: "reset_required",
|
|
2301
|
-
|
|
2354
|
+
sync_capability: LOCAL_SYNC_CAPABILITY,
|
|
2355
|
+
...addSyncScopeToBoundary(result.reset, scopeRequest.scope_id)
|
|
2302
2356
|
}, 409);
|
|
2303
2357
|
const { ops, nextCursor, boundary } = result;
|
|
2304
|
-
const filtered = filterOpsForPeer(store, peerDeviceId, ops);
|
|
2358
|
+
const filtered = filterOpsForPeer(store, peerDeviceId, localDeviceId, ops);
|
|
2305
2359
|
return c.json({
|
|
2306
2360
|
reset_required: false,
|
|
2361
|
+
sync_capability: LOCAL_SYNC_CAPABILITY,
|
|
2362
|
+
scope_id: scopeRequest.scope_id,
|
|
2307
2363
|
generation: boundary.generation,
|
|
2308
2364
|
snapshot_id: boundary.snapshot_id,
|
|
2309
2365
|
baseline_cursor: boundary.baseline_cursor,
|
|
@@ -2343,6 +2399,9 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2343
2399
|
const limited = rateLimitedResponse(c, auth.deviceId, true);
|
|
2344
2400
|
if (limited) return limited;
|
|
2345
2401
|
try {
|
|
2402
|
+
const rawScopeId = c.req.query(SYNC_SCOPE_QUERY_PARAM);
|
|
2403
|
+
const scopeRequest = parseSyncScopeRequest(rawScopeId, rawScopeId !== void 0);
|
|
2404
|
+
if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY), 409);
|
|
2346
2405
|
const rawGeneration = c.req.query("generation");
|
|
2347
2406
|
const generation = rawGeneration != null && rawGeneration.trim().length > 0 ? Number.parseInt(rawGeneration, 10) : null;
|
|
2348
2407
|
const snapshotId = c.req.query("snapshot_id") ?? null;
|
|
@@ -2361,10 +2420,12 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2361
2420
|
peerDeviceId: auth.deviceId
|
|
2362
2421
|
});
|
|
2363
2422
|
return c.json({
|
|
2423
|
+
scope_id: scopeRequest.scope_id,
|
|
2364
2424
|
generation: result.boundary.generation,
|
|
2365
2425
|
snapshot_id: result.boundary.snapshot_id,
|
|
2366
2426
|
baseline_cursor: result.boundary.baseline_cursor,
|
|
2367
2427
|
retained_floor_cursor: result.boundary.retained_floor_cursor,
|
|
2428
|
+
sync_capability: LOCAL_SYNC_CAPABILITY,
|
|
2368
2429
|
items: result.items,
|
|
2369
2430
|
next_page_token: result.nextPageToken,
|
|
2370
2431
|
has_more: result.hasMore
|
|
@@ -2393,6 +2454,8 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2393
2454
|
} catch {
|
|
2394
2455
|
return c.json({ error: "invalid_json" }, 400);
|
|
2395
2456
|
}
|
|
2457
|
+
const scopeRequest = parseSyncScopeRequest(body.scope_id, Object.hasOwn(body, "scope_id"));
|
|
2458
|
+
if (!scopeRequest.ok) return c.json(syncScopeResetRequiredPayload(getSyncResetState(store.db), scopeRequest.reason, LOCAL_SYNC_CAPABILITY), 409);
|
|
2396
2459
|
if (!Array.isArray(body.ops)) return c.json({ error: "invalid_ops" }, 400);
|
|
2397
2460
|
if (body.ops.length > MAX_SYNC_OPS) return c.json({ error: "too_many_ops" }, 413);
|
|
2398
2461
|
const normalizedOps = extractReplicationOps(body);
|
|
@@ -2402,11 +2465,26 @@ function syncProtocolRoutes(getStore, opts = {}) {
|
|
|
2402
2465
|
op_id: op.op_id
|
|
2403
2466
|
}, 400);
|
|
2404
2467
|
const [localDeviceId] = ensureDeviceIdentity(store.db, { keysDir: syncKeysDir() });
|
|
2405
|
-
const
|
|
2406
|
-
const
|
|
2468
|
+
const negotiatedCapability = negotiateSyncCapability(LOCAL_SYNC_CAPABILITY, normalizeSyncCapability(body.sync_capability));
|
|
2469
|
+
const filtered = filterOpsForPeer(store, peerDeviceId, localDeviceId, normalizedOps, { applyScopeFilter: false });
|
|
2470
|
+
const rejected = rejectInboundScopeFailures(store.db, normalizedOps, localDeviceId, {
|
|
2471
|
+
enabled: negotiatedCapability !== "unsupported",
|
|
2472
|
+
peerDeviceId
|
|
2473
|
+
});
|
|
2474
|
+
if (rejected) return c.json({
|
|
2475
|
+
error: "scope_rejected",
|
|
2476
|
+
reason: rejected.rejections[0]?.reason ?? "scope_mismatch",
|
|
2477
|
+
rejections: rejected.rejections,
|
|
2478
|
+
sync_capability: LOCAL_SYNC_CAPABILITY,
|
|
2479
|
+
scope_id: scopeRequest.scope_id
|
|
2480
|
+
}, 403);
|
|
2481
|
+
const result = applyReplicationOps(store.db, filtered.allowed, localDeviceId, store.scanner);
|
|
2482
|
+
const skipped = result.skipped + filtered.skipped;
|
|
2407
2483
|
return c.json({
|
|
2408
2484
|
...result,
|
|
2409
|
-
skipped
|
|
2485
|
+
skipped,
|
|
2486
|
+
sync_capability: LOCAL_SYNC_CAPABILITY,
|
|
2487
|
+
scope_id: scopeRequest.scope_id
|
|
2410
2488
|
});
|
|
2411
2489
|
});
|
|
2412
2490
|
return app;
|
|
@@ -2509,8 +2587,9 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2509
2587
|
}
|
|
2510
2588
|
const peerRows = traceSync("peerRows", () => store.db.prepare(PEERS_QUERY).all());
|
|
2511
2589
|
const recentOpsByPeer = traceSync("recentPeerOps", () => recentPeerOps(store));
|
|
2590
|
+
const scopeRejectionsByPeer = traceSync("recentScopeRejectionsByPeer", () => recentScopeRejectionsByPeer(store));
|
|
2512
2591
|
const peersItems = peerRows.map((row) => {
|
|
2513
|
-
const peer = mapPeerRow(store, row, showDiag, recentOpsByPeer);
|
|
2592
|
+
const peer = mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer);
|
|
2514
2593
|
peer.status = peerStatus(peer);
|
|
2515
2594
|
return peer;
|
|
2516
2595
|
});
|
|
@@ -2523,7 +2602,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2523
2602
|
started_at: schema.syncAttempts.started_at,
|
|
2524
2603
|
finished_at: schema.syncAttempts.finished_at,
|
|
2525
2604
|
ops_in: schema.syncAttempts.ops_in,
|
|
2526
|
-
ops_out: schema.syncAttempts.ops_out
|
|
2605
|
+
ops_out: schema.syncAttempts.ops_out,
|
|
2606
|
+
local_sync_capability: schema.syncAttempts.local_sync_capability,
|
|
2607
|
+
peer_sync_capability: schema.syncAttempts.peer_sync_capability,
|
|
2608
|
+
negotiated_sync_capability: schema.syncAttempts.negotiated_sync_capability
|
|
2527
2609
|
}).from(schema.syncAttempts).orderBy(desc(schema.syncAttempts.finished_at)).limit(25).all());
|
|
2528
2610
|
const peerAddressMap = /* @__PURE__ */ new Map();
|
|
2529
2611
|
traceSync("peerAddressRows", () => store.db.prepare("SELECT peer_device_id, addresses_json FROM sync_peers").all()).forEach((peerRow) => {
|
|
@@ -2597,13 +2679,43 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2597
2679
|
const showDiag = queryBool(c.req.query("includeDiagnostics"));
|
|
2598
2680
|
const rows = store.db.prepare(PEERS_QUERY).all();
|
|
2599
2681
|
const recentOpsByPeer = recentPeerOps(store);
|
|
2600
|
-
const
|
|
2682
|
+
const scopeRejectionsByPeer = recentScopeRejectionsByPeer(store);
|
|
2683
|
+
const peers = rows.map((row) => mapPeerRow(store, row, showDiag, recentOpsByPeer, scopeRejectionsByPeer));
|
|
2601
2684
|
return c.json({
|
|
2602
2685
|
items: peers,
|
|
2603
2686
|
redacted: !showDiag
|
|
2604
2687
|
});
|
|
2605
2688
|
}
|
|
2606
2689
|
});
|
|
2690
|
+
app.get("/api/sync/peers/:peer_device_id/scope-rejections", (c) => {
|
|
2691
|
+
const store = getStore();
|
|
2692
|
+
const peerDeviceId = String(c.req.param("peer_device_id") || "").trim();
|
|
2693
|
+
if (!peerDeviceId) return c.json({ error: "missing_peer_device_id" }, 400);
|
|
2694
|
+
const limit = queryInt(c.req.query("limit"), 100);
|
|
2695
|
+
if (limit <= 0) return c.json({ error: "invalid_limit" }, 400);
|
|
2696
|
+
const sinceMinutesRaw = queryInt(c.req.query("sinceMinutes"), 1440);
|
|
2697
|
+
const sinceMinutes = Math.min(Math.max(sinceMinutesRaw, 0), 10 * 365 * 24 * 60);
|
|
2698
|
+
const sinceIso = sinceMinutes > 0 ? (/* @__PURE__ */ new Date(Date.now() - sinceMinutes * 6e4)).toISOString() : void 0;
|
|
2699
|
+
const records = listInboundScopeRejections(store.db, {
|
|
2700
|
+
peerDeviceId,
|
|
2701
|
+
sinceIso,
|
|
2702
|
+
limit
|
|
2703
|
+
});
|
|
2704
|
+
const summary = summarizeInboundScopeRejections(store.db, {
|
|
2705
|
+
peerDeviceId,
|
|
2706
|
+
sinceIso
|
|
2707
|
+
})[0] ?? {
|
|
2708
|
+
peer_device_id: peerDeviceId,
|
|
2709
|
+
total: 0,
|
|
2710
|
+
by_reason: {},
|
|
2711
|
+
last_at: null
|
|
2712
|
+
};
|
|
2713
|
+
return c.json({
|
|
2714
|
+
peer_device_id: peerDeviceId,
|
|
2715
|
+
summary,
|
|
2716
|
+
items: records
|
|
2717
|
+
});
|
|
2718
|
+
});
|
|
2607
2719
|
app.get("/api/sync/actors", (c) => {
|
|
2608
2720
|
const store = getStore();
|
|
2609
2721
|
{
|
|
@@ -2629,7 +2741,10 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
2629
2741
|
started_at: schema.syncAttempts.started_at,
|
|
2630
2742
|
finished_at: schema.syncAttempts.finished_at,
|
|
2631
2743
|
ops_in: schema.syncAttempts.ops_in,
|
|
2632
|
-
ops_out: schema.syncAttempts.ops_out
|
|
2744
|
+
ops_out: schema.syncAttempts.ops_out,
|
|
2745
|
+
local_sync_capability: schema.syncAttempts.local_sync_capability,
|
|
2746
|
+
peer_sync_capability: schema.syncAttempts.peer_sync_capability,
|
|
2747
|
+
negotiated_sync_capability: schema.syncAttempts.negotiated_sync_capability
|
|
2633
2748
|
}).from(schema.syncAttempts).orderBy(desc(schema.syncAttempts.finished_at)).limit(limit).all();
|
|
2634
2749
|
return c.json({ items: rows });
|
|
2635
2750
|
}
|
|
@@ -3052,7 +3167,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3052
3167
|
return c.json({
|
|
3053
3168
|
error: message,
|
|
3054
3169
|
status
|
|
3055
|
-
},
|
|
3170
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3056
3171
|
}
|
|
3057
3172
|
});
|
|
3058
3173
|
app.post("/api/coordinator/admin/groups", async (c) => {
|
|
@@ -3229,6 +3344,304 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3229
3344
|
}, 400);
|
|
3230
3345
|
}
|
|
3231
3346
|
});
|
|
3347
|
+
app.get("/api/coordinator/admin/groups/:group_id/scopes", async (c) => {
|
|
3348
|
+
const config = readCoordinatorSyncConfig();
|
|
3349
|
+
const status = coordinatorAdminStatusPayload(config);
|
|
3350
|
+
const unavailable = coordinatorAdminUnavailable(status);
|
|
3351
|
+
if (unavailable) return c.json(unavailable.body, unavailable.httpStatus);
|
|
3352
|
+
const groupId = String(c.req.param("group_id") ?? "").trim();
|
|
3353
|
+
if (!groupId) return c.json({
|
|
3354
|
+
error: "group_id required",
|
|
3355
|
+
status
|
|
3356
|
+
}, 400);
|
|
3357
|
+
try {
|
|
3358
|
+
const items = await coordinatorListScopesAction({
|
|
3359
|
+
groupId,
|
|
3360
|
+
includeInactive: queryBool(c.req.query("include_inactive")),
|
|
3361
|
+
remoteUrl: config.syncCoordinatorUrl || null,
|
|
3362
|
+
adminSecret: config.syncCoordinatorAdminSecret || null
|
|
3363
|
+
});
|
|
3364
|
+
return c.json({
|
|
3365
|
+
items,
|
|
3366
|
+
group_id: groupId,
|
|
3367
|
+
status
|
|
3368
|
+
});
|
|
3369
|
+
} catch (error) {
|
|
3370
|
+
const message = error instanceof Error ? error.message : "unknown";
|
|
3371
|
+
return c.json({
|
|
3372
|
+
error: message,
|
|
3373
|
+
status
|
|
3374
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3375
|
+
}
|
|
3376
|
+
});
|
|
3377
|
+
app.post("/api/coordinator/admin/groups/:group_id/scopes", async (c) => {
|
|
3378
|
+
const config = readCoordinatorSyncConfig();
|
|
3379
|
+
const status = coordinatorAdminStatusPayload(config);
|
|
3380
|
+
const unavailable = coordinatorAdminUnavailable(status);
|
|
3381
|
+
if (unavailable) return c.json(unavailable.body, unavailable.httpStatus);
|
|
3382
|
+
const groupId = String(c.req.param("group_id") ?? "").trim();
|
|
3383
|
+
if (!groupId) return c.json({
|
|
3384
|
+
error: "group_id required",
|
|
3385
|
+
status
|
|
3386
|
+
}, 400);
|
|
3387
|
+
const body = await parseViewerJsonBody(c);
|
|
3388
|
+
if (!body) return c.json({
|
|
3389
|
+
error: "invalid json",
|
|
3390
|
+
status
|
|
3391
|
+
}, 400);
|
|
3392
|
+
const scopeId = optionalViewerString(body, "scope_id");
|
|
3393
|
+
const label = optionalViewerString(body, "label");
|
|
3394
|
+
const membershipEpoch = optionalViewerNumber(body, "membership_epoch");
|
|
3395
|
+
if (!scopeId || !label) return c.json({
|
|
3396
|
+
error: "scope_id and label required",
|
|
3397
|
+
status
|
|
3398
|
+
}, 400);
|
|
3399
|
+
if (Number.isNaN(membershipEpoch)) return c.json({
|
|
3400
|
+
error: "membership_epoch must be number",
|
|
3401
|
+
status
|
|
3402
|
+
}, 400);
|
|
3403
|
+
try {
|
|
3404
|
+
const scope = await coordinatorCreateScopeAction({
|
|
3405
|
+
groupId,
|
|
3406
|
+
scopeId,
|
|
3407
|
+
label,
|
|
3408
|
+
kind: optionalViewerString(body, "kind"),
|
|
3409
|
+
authorityType: optionalViewerString(body, "authority_type"),
|
|
3410
|
+
coordinatorId: optionalViewerString(body, "coordinator_id"),
|
|
3411
|
+
manifestIssuerDeviceId: optionalViewerString(body, "manifest_issuer_device_id"),
|
|
3412
|
+
membershipEpoch,
|
|
3413
|
+
manifestHash: optionalViewerString(body, "manifest_hash"),
|
|
3414
|
+
status: optionalViewerString(body, "status"),
|
|
3415
|
+
remoteUrl: config.syncCoordinatorUrl || null,
|
|
3416
|
+
adminSecret: config.syncCoordinatorAdminSecret || null
|
|
3417
|
+
});
|
|
3418
|
+
return c.json({
|
|
3419
|
+
ok: true,
|
|
3420
|
+
scope,
|
|
3421
|
+
group_id: groupId,
|
|
3422
|
+
status
|
|
3423
|
+
});
|
|
3424
|
+
} catch (error) {
|
|
3425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3426
|
+
return c.json({
|
|
3427
|
+
error: message,
|
|
3428
|
+
status
|
|
3429
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3432
|
+
app.patch("/api/coordinator/admin/groups/:group_id/scopes/:scope_id", async (c) => {
|
|
3433
|
+
const config = readCoordinatorSyncConfig();
|
|
3434
|
+
const status = coordinatorAdminStatusPayload(config);
|
|
3435
|
+
const unavailable = coordinatorAdminUnavailable(status);
|
|
3436
|
+
if (unavailable) return c.json(unavailable.body, unavailable.httpStatus);
|
|
3437
|
+
const groupId = String(c.req.param("group_id") ?? "").trim();
|
|
3438
|
+
const scopeId = String(c.req.param("scope_id") ?? "").trim();
|
|
3439
|
+
if (!groupId) return c.json({
|
|
3440
|
+
error: "group_id required",
|
|
3441
|
+
status
|
|
3442
|
+
}, 400);
|
|
3443
|
+
if (!scopeId) return c.json({
|
|
3444
|
+
error: "scope_id required",
|
|
3445
|
+
status
|
|
3446
|
+
}, 400);
|
|
3447
|
+
const body = await parseViewerJsonBody(c);
|
|
3448
|
+
if (!body) return c.json({
|
|
3449
|
+
error: "invalid json",
|
|
3450
|
+
status
|
|
3451
|
+
}, 400);
|
|
3452
|
+
const membershipEpoch = optionalViewerNumber(body, "membership_epoch");
|
|
3453
|
+
if (Number.isNaN(membershipEpoch)) return c.json({
|
|
3454
|
+
error: "membership_epoch must be number",
|
|
3455
|
+
status
|
|
3456
|
+
}, 400);
|
|
3457
|
+
try {
|
|
3458
|
+
const scope = await coordinatorUpdateScopeAction({
|
|
3459
|
+
groupId,
|
|
3460
|
+
scopeId,
|
|
3461
|
+
label: body.label === void 0 ? void 0 : optionalViewerString(body, "label"),
|
|
3462
|
+
kind: body.kind === void 0 ? void 0 : optionalViewerString(body, "kind"),
|
|
3463
|
+
authorityType: body.authority_type === void 0 ? void 0 : optionalViewerString(body, "authority_type"),
|
|
3464
|
+
coordinatorId: body.coordinator_id === void 0 ? void 0 : optionalViewerString(body, "coordinator_id"),
|
|
3465
|
+
manifestIssuerDeviceId: body.manifest_issuer_device_id === void 0 ? void 0 : optionalViewerString(body, "manifest_issuer_device_id"),
|
|
3466
|
+
membershipEpoch,
|
|
3467
|
+
manifestHash: body.manifest_hash === void 0 ? void 0 : optionalViewerString(body, "manifest_hash"),
|
|
3468
|
+
status: body.status === void 0 ? void 0 : optionalViewerString(body, "status"),
|
|
3469
|
+
remoteUrl: config.syncCoordinatorUrl || null,
|
|
3470
|
+
adminSecret: config.syncCoordinatorAdminSecret || null
|
|
3471
|
+
});
|
|
3472
|
+
if (!scope) return c.json({
|
|
3473
|
+
error: "scope_not_found",
|
|
3474
|
+
status
|
|
3475
|
+
}, 404);
|
|
3476
|
+
return c.json({
|
|
3477
|
+
ok: true,
|
|
3478
|
+
scope,
|
|
3479
|
+
group_id: groupId,
|
|
3480
|
+
status
|
|
3481
|
+
});
|
|
3482
|
+
} catch (error) {
|
|
3483
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3484
|
+
return c.json({
|
|
3485
|
+
error: message,
|
|
3486
|
+
status
|
|
3487
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3488
|
+
}
|
|
3489
|
+
});
|
|
3490
|
+
app.get("/api/coordinator/admin/groups/:group_id/scopes/:scope_id/members", async (c) => {
|
|
3491
|
+
const config = readCoordinatorSyncConfig();
|
|
3492
|
+
const status = coordinatorAdminStatusPayload(config);
|
|
3493
|
+
const unavailable = coordinatorAdminUnavailable(status);
|
|
3494
|
+
if (unavailable) return c.json(unavailable.body, unavailable.httpStatus);
|
|
3495
|
+
const groupId = String(c.req.param("group_id") ?? "").trim();
|
|
3496
|
+
const scopeId = String(c.req.param("scope_id") ?? "").trim();
|
|
3497
|
+
if (!groupId) return c.json({
|
|
3498
|
+
error: "group_id required",
|
|
3499
|
+
status
|
|
3500
|
+
}, 400);
|
|
3501
|
+
if (!scopeId) return c.json({
|
|
3502
|
+
error: "scope_id required",
|
|
3503
|
+
status
|
|
3504
|
+
}, 400);
|
|
3505
|
+
try {
|
|
3506
|
+
const items = await coordinatorListScopeMembershipsAction({
|
|
3507
|
+
groupId,
|
|
3508
|
+
scopeId,
|
|
3509
|
+
includeRevoked: queryBool(c.req.query("include_revoked")),
|
|
3510
|
+
remoteUrl: config.syncCoordinatorUrl || null,
|
|
3511
|
+
adminSecret: config.syncCoordinatorAdminSecret || null
|
|
3512
|
+
});
|
|
3513
|
+
return c.json({
|
|
3514
|
+
items,
|
|
3515
|
+
group_id: groupId,
|
|
3516
|
+
scope_id: scopeId,
|
|
3517
|
+
status
|
|
3518
|
+
});
|
|
3519
|
+
} catch (error) {
|
|
3520
|
+
const message = error instanceof Error ? error.message : "unknown";
|
|
3521
|
+
return c.json({
|
|
3522
|
+
error: message,
|
|
3523
|
+
status
|
|
3524
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3525
|
+
}
|
|
3526
|
+
});
|
|
3527
|
+
app.post("/api/coordinator/admin/groups/:group_id/scopes/:scope_id/members", async (c) => {
|
|
3528
|
+
const config = readCoordinatorSyncConfig();
|
|
3529
|
+
const status = coordinatorAdminStatusPayload(config);
|
|
3530
|
+
const unavailable = coordinatorAdminUnavailable(status);
|
|
3531
|
+
if (unavailable) return c.json(unavailable.body, unavailable.httpStatus);
|
|
3532
|
+
const groupId = String(c.req.param("group_id") ?? "").trim();
|
|
3533
|
+
const scopeId = String(c.req.param("scope_id") ?? "").trim();
|
|
3534
|
+
if (!groupId) return c.json({
|
|
3535
|
+
error: "group_id required",
|
|
3536
|
+
status
|
|
3537
|
+
}, 400);
|
|
3538
|
+
if (!scopeId) return c.json({
|
|
3539
|
+
error: "scope_id required",
|
|
3540
|
+
status
|
|
3541
|
+
}, 400);
|
|
3542
|
+
const body = await parseViewerJsonBody(c);
|
|
3543
|
+
if (!body) return c.json({
|
|
3544
|
+
error: "invalid json",
|
|
3545
|
+
status
|
|
3546
|
+
}, 400);
|
|
3547
|
+
const deviceId = optionalViewerString(body, "device_id");
|
|
3548
|
+
const membershipEpoch = optionalViewerNumber(body, "membership_epoch");
|
|
3549
|
+
if (!deviceId) return c.json({
|
|
3550
|
+
error: "device_id required",
|
|
3551
|
+
status
|
|
3552
|
+
}, 400);
|
|
3553
|
+
if (Number.isNaN(membershipEpoch)) return c.json({
|
|
3554
|
+
error: "membership_epoch must be number",
|
|
3555
|
+
status
|
|
3556
|
+
}, 400);
|
|
3557
|
+
try {
|
|
3558
|
+
const membership = await coordinatorGrantScopeMembershipAction({
|
|
3559
|
+
groupId,
|
|
3560
|
+
scopeId,
|
|
3561
|
+
deviceId,
|
|
3562
|
+
role: optionalViewerString(body, "role"),
|
|
3563
|
+
membershipEpoch,
|
|
3564
|
+
coordinatorId: optionalViewerString(body, "coordinator_id"),
|
|
3565
|
+
manifestIssuerDeviceId: optionalViewerString(body, "manifest_issuer_device_id"),
|
|
3566
|
+
manifestHash: optionalViewerString(body, "manifest_hash"),
|
|
3567
|
+
signedManifestJson: optionalViewerString(body, "signed_manifest_json"),
|
|
3568
|
+
remoteUrl: config.syncCoordinatorUrl || null,
|
|
3569
|
+
adminSecret: config.syncCoordinatorAdminSecret || null
|
|
3570
|
+
});
|
|
3571
|
+
return c.json({
|
|
3572
|
+
ok: true,
|
|
3573
|
+
membership,
|
|
3574
|
+
group_id: groupId,
|
|
3575
|
+
scope_id: scopeId,
|
|
3576
|
+
status
|
|
3577
|
+
});
|
|
3578
|
+
} catch (error) {
|
|
3579
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3580
|
+
return c.json({
|
|
3581
|
+
error: message,
|
|
3582
|
+
status
|
|
3583
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
app.post("/api/coordinator/admin/groups/:group_id/scopes/:scope_id/members/:device_id/revoke", async (c) => {
|
|
3587
|
+
const config = readCoordinatorSyncConfig();
|
|
3588
|
+
const status = coordinatorAdminStatusPayload(config);
|
|
3589
|
+
const unavailable = coordinatorAdminUnavailable(status);
|
|
3590
|
+
if (unavailable) return c.json(unavailable.body, unavailable.httpStatus);
|
|
3591
|
+
const groupId = String(c.req.param("group_id") ?? "").trim();
|
|
3592
|
+
const scopeId = String(c.req.param("scope_id") ?? "").trim();
|
|
3593
|
+
const deviceId = String(c.req.param("device_id") ?? "").trim();
|
|
3594
|
+
if (!groupId) return c.json({
|
|
3595
|
+
error: "group_id required",
|
|
3596
|
+
status
|
|
3597
|
+
}, 400);
|
|
3598
|
+
if (!scopeId) return c.json({
|
|
3599
|
+
error: "scope_id required",
|
|
3600
|
+
status
|
|
3601
|
+
}, 400);
|
|
3602
|
+
if (!deviceId) return c.json({
|
|
3603
|
+
error: "device_id required",
|
|
3604
|
+
status
|
|
3605
|
+
}, 400);
|
|
3606
|
+
const body = await parseViewerJsonBody(c, { allowEmpty: true });
|
|
3607
|
+
if (!body) return c.json({
|
|
3608
|
+
error: "invalid json",
|
|
3609
|
+
status
|
|
3610
|
+
}, 400);
|
|
3611
|
+
const membershipEpoch = optionalViewerNumber(body, "membership_epoch");
|
|
3612
|
+
if (Number.isNaN(membershipEpoch)) return c.json({
|
|
3613
|
+
error: "membership_epoch must be number",
|
|
3614
|
+
status
|
|
3615
|
+
}, 400);
|
|
3616
|
+
try {
|
|
3617
|
+
if (!await coordinatorRevokeScopeMembershipAction({
|
|
3618
|
+
groupId,
|
|
3619
|
+
scopeId,
|
|
3620
|
+
deviceId,
|
|
3621
|
+
membershipEpoch,
|
|
3622
|
+
manifestHash: optionalViewerString(body, "manifest_hash"),
|
|
3623
|
+
signedManifestJson: optionalViewerString(body, "signed_manifest_json"),
|
|
3624
|
+
remoteUrl: config.syncCoordinatorUrl || null,
|
|
3625
|
+
adminSecret: config.syncCoordinatorAdminSecret || null
|
|
3626
|
+
})) return c.json({
|
|
3627
|
+
error: "membership_not_found",
|
|
3628
|
+
status
|
|
3629
|
+
}, 404);
|
|
3630
|
+
return c.json({
|
|
3631
|
+
ok: true,
|
|
3632
|
+
group_id: groupId,
|
|
3633
|
+
scope_id: scopeId,
|
|
3634
|
+
device_id: deviceId,
|
|
3635
|
+
status
|
|
3636
|
+
});
|
|
3637
|
+
} catch (error) {
|
|
3638
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3639
|
+
return c.json({
|
|
3640
|
+
error: message,
|
|
3641
|
+
status
|
|
3642
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3232
3645
|
app.post("/api/coordinator/admin/invites", async (c) => {
|
|
3233
3646
|
const config = readCoordinatorSyncConfig();
|
|
3234
3647
|
const status = coordinatorAdminStatusPayload(config);
|
|
@@ -3322,7 +3735,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3322
3735
|
return c.json({
|
|
3323
3736
|
error: message,
|
|
3324
3737
|
status
|
|
3325
|
-
},
|
|
3738
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3326
3739
|
}
|
|
3327
3740
|
});
|
|
3328
3741
|
app.post("/api/coordinator/admin/join-requests/:request_id/approve", async (c) => {
|
|
@@ -3440,7 +3853,7 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
|
|
|
3440
3853
|
return c.json({
|
|
3441
3854
|
error: message,
|
|
3442
3855
|
status
|
|
3443
|
-
},
|
|
3856
|
+
}, coordinatorAdminMutationStatus(message));
|
|
3444
3857
|
}
|
|
3445
3858
|
});
|
|
3446
3859
|
app.post("/api/coordinator/admin/devices/:device_id/rename", async (c) => {
|