From 220347fb9071ea97c0caef972810b7ebc5200513 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 07:11:47 -0500 Subject: [PATCH 01/22] fix: tests running until timeout because of missing count in while loop for query end to end test. --- tests/end-to-end/test_api_query.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end-to-end/test_api_query.py b/tests/end-to-end/test_api_query.py index cf1894a09..174d0d4c6 100755 --- a/tests/end-to-end/test_api_query.py +++ b/tests/end-to-end/test_api_query.py @@ -191,6 +191,7 @@ def test_query_create_delete(self): if model.alias.startswith("adamantium"): material = model.alias time.sleep(self._timeout) + count = count + 1 print(f"Query found {material}") From 24f15a11c7c79113c9d4c6500c96580845300eea Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 07:50:39 -0500 Subject: [PATCH 02/22] refactor: query router add defensive check. --- core/database/foxx/api/query_router.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/database/foxx/api/query_router.js b/core/database/foxx/api/query_router.js index 88e3accb4..b1b26d1e1 100644 --- a/core/database/foxx/api/query_router.js +++ b/core/database/foxx/api/query_router.js @@ -660,6 +660,15 @@ router var qry = g_db.q.document(req.queryParams.id); + // Legacy query documents may have `params` stored as a JSON string + // rather than an object, because the original schema validation + // (joi.any()) accepted both. New documents are stored as objects + // (joi.object()), but old records remain until migrated. + // TODO: Remove after backfilling existing queries in ArangoDB. + if (typeof qry.params === "string") { + qry.params = JSON.parse(qry.params); + } + if (client._id != qry.owner && !client.is_admin) { throw error.ERR_PERM_DENIED; } From e6fe881d6a275c8a25e1f07254703b788c4703bb Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 09:50:52 -0500 Subject: [PATCH 03/22] fix: update submodules --- external/DataFedDependencies | 2 +- external/globus-connect-server-deploy | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/DataFedDependencies b/external/DataFedDependencies index fe59a393f..e0319a2f2 160000 --- a/external/DataFedDependencies +++ b/external/DataFedDependencies @@ -1 +1 @@ -Subproject commit fe59a393f54d3aa1b8bf551f97d274b762bf93d2 +Subproject commit e0319a2f2e70d901180c730ebd5b10b10d8fce93 diff --git a/external/globus-connect-server-deploy b/external/globus-connect-server-deploy index 436b396c4..ff7167860 160000 --- a/external/globus-connect-server-deploy +++ b/external/globus-connect-server-deploy @@ -1 +1 @@ -Subproject commit 436b396c4da6d141c9c0534b297b5e43cc9ac35c +Subproject commit ff7167860345e9b994110dfabdb251fe4dea8c00 From 911aa14306e7d04f8933ec2de77eb7ce81e1d621 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 12:44:48 -0500 Subject: [PATCH 04/22] fix: fix missing proto2->proto3 transition setting. --- core/server/DatabaseAPI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/server/DatabaseAPI.cpp b/core/server/DatabaseAPI.cpp index 4dd2be945..5acd8da9e 100644 --- a/core/server/DatabaseAPI.cpp +++ b/core/server/DatabaseAPI.cpp @@ -1762,6 +1762,7 @@ void DatabaseAPI::queryCreate(const SDMS::QueryCreateRequest &a_request, options.always_print_enums_as_ints = true; options.preserve_proto_field_names = true; + options.always_print_primitive_fields = true; auto stat = google::protobuf::util::MessageToJsonString(a_request.query(), &query_json, options); @@ -1806,6 +1807,7 @@ void DatabaseAPI::queryUpdate(const SDMS::QueryUpdateRequest &a_request, options.always_print_enums_as_ints = true; options.preserve_proto_field_names = true; + options.always_print_primitive_fields = true; auto stat = google::protobuf::util::MessageToJsonString( a_request.query(), &query_json, options); From 6c38b704ada2117f0151eea304334cdbd587dfa1 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 13:53:56 -0500 Subject: [PATCH 05/22] fix: support python client partial updates. --- core/server/DatabaseAPI.cpp | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/core/server/DatabaseAPI.cpp b/core/server/DatabaseAPI.cpp index 5acd8da9e..854495d44 100644 --- a/core/server/DatabaseAPI.cpp +++ b/core/server/DatabaseAPI.cpp @@ -1797,20 +1797,42 @@ void DatabaseAPI::queryUpdate(const SDMS::QueryUpdateRequest &a_request, } if (a_request.has_query()) { - string qry_begin, qry_end, qry_filter, params; + // Fetch existing stored query + Value existing; + dbGet("qry/view", {{"id", a_request.id()}}, existing, log_context); + + // Parse stored query JSON into a SearchRequest + SDMS::SearchRequest merged; + auto parse_stat = google::protobuf::util::JsonStringToMessage( + existing.asObject().getValue("query").toString(), &merged); + if (!parse_stat.ok()) { + EXCEPT(1, "Failed to parse existing query"); + } + + if (a_request.query().coll_size() > 0) { + merged.clear_coll(); + } + if (a_request.query().tags_size() > 0) { + merged.clear_tags(); + } + if (a_request.query().cat_tags_size() > 0) { + merged.clear_cat_tags(); + } + // Incoming fields overwrite existing, unset fields left untouched + merged.MergeFrom(a_request.query()); - uint32_t cnt = parseSearchRequest(a_request.query(), qry_begin, qry_end, + // Re-generate AQL from the complete merged query + string qry_begin, qry_end, qry_filter, params; + uint32_t cnt = parseSearchRequest(merged, qry_begin, qry_end, qry_filter, params, log_context); google::protobuf::util::JsonPrintOptions options; string query_json; - options.always_print_enums_as_ints = true; options.preserve_proto_field_names = true; options.always_print_primitive_fields = true; - auto stat = google::protobuf::util::MessageToJsonString( - a_request.query(), &query_json, options); + merged, &query_json, options); if (!stat.ok()) { EXCEPT(1, "Invalid search request"); } @@ -1825,7 +1847,6 @@ void DatabaseAPI::queryUpdate(const SDMS::QueryUpdateRequest &a_request, string body = payload.dump(-1, ' ', true); dbPost("qry/update", {}, &body, result, log_context); - setQueryData(a_reply, result, log_context); } From ac2aeea4bee9b4b3b7cd0319ef51f6322e295ceb Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 16:59:18 -0500 Subject: [PATCH 06/22] fix: add attribute to query update to distinguish partial replace from full document replace. --- common/proto3/common/auth/query_update_request.proto | 1 + core/database/foxx/api/user_router.js | 2 ++ web/datafed-ws.js | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common/proto3/common/auth/query_update_request.proto b/common/proto3/common/auth/query_update_request.proto index 518a93ce7..01092275d 100644 --- a/common/proto3/common/auth/query_update_request.proto +++ b/common/proto3/common/auth/query_update_request.proto @@ -10,4 +10,5 @@ message QueryUpdateRequest { string id = 1; optional string title = 2; SearchRequest query = 3; + bool replace_query = 4; } diff --git a/core/database/foxx/api/user_router.js b/core/database/foxx/api/user_router.js index fce626e90..67be78457 100644 --- a/core/database/foxx/api/user_router.js +++ b/core/database/foxx/api/user_router.js @@ -314,6 +314,8 @@ router router .get("/update", function (req, res) { + + let client = null; let result = null; let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; try { diff --git a/web/datafed-ws.js b/web/datafed-ws.js index 6b367565e..055c107da 100755 --- a/web/datafed-ws.js +++ b/web/datafed-ws.js @@ -1114,7 +1114,7 @@ app.post("/api/query/create", (a_req, a_resp) => { }); app.post("/api/query/update", (a_req, a_resp) => { - var params = { id: a_req.query.id }; + var params = { id: a_req.query.id, replaceQuery: true }; if (a_req.query.title) params.title = a_req.query.title; if (a_req.body) params.query = a_req.body; From 8e66b43eb071831e719866de8f9d941730370754 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 17:02:18 -0500 Subject: [PATCH 07/22] fix: support partial and full replacement in core server for query. --- core/server/DatabaseAPI.cpp | 49 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/core/server/DatabaseAPI.cpp b/core/server/DatabaseAPI.cpp index 854495d44..088486d46 100644 --- a/core/server/DatabaseAPI.cpp +++ b/core/server/DatabaseAPI.cpp @@ -1797,33 +1797,36 @@ void DatabaseAPI::queryUpdate(const SDMS::QueryUpdateRequest &a_request, } if (a_request.has_query()) { - // Fetch existing stored query - Value existing; - dbGet("qry/view", {{"id", a_request.id()}}, existing, log_context); - - // Parse stored query JSON into a SearchRequest - SDMS::SearchRequest merged; - auto parse_stat = google::protobuf::util::JsonStringToMessage( - existing.asObject().getValue("query").toString(), &merged); - if (!parse_stat.ok()) { - EXCEPT(1, "Failed to parse existing query"); - } + SDMS::SearchRequest final_query; + if (a_request.replace_query()) { + // Full replacement — use incoming query as-is + final_query.CopyFrom(a_request.query()); + } else { + // Partial update — merge incoming onto existing + Value existing; + dbGet("qry/view", {{"id", a_request.id()}}, existing, log_context); + + auto parse_stat = google::protobuf::util::JsonStringToMessage( + existing.asObject().getValue("query").toString(), &final_query); + if (!parse_stat.ok()) { + EXCEPT(1, "Failed to parse existing query"); + } - if (a_request.query().coll_size() > 0) { - merged.clear_coll(); - } - if (a_request.query().tags_size() > 0) { - merged.clear_tags(); - } - if (a_request.query().cat_tags_size() > 0) { - merged.clear_cat_tags(); + if (a_request.query().coll_size() > 0) { + final_query.clear_coll(); + } + if (a_request.query().tags_size() > 0) { + final_query.clear_tags(); + } + if (a_request.query().cat_tags_size() > 0) { + final_query.clear_cat_tags(); + } + final_query.MergeFrom(a_request.query()); } - // Incoming fields overwrite existing, unset fields left untouched - merged.MergeFrom(a_request.query()); // Re-generate AQL from the complete merged query string qry_begin, qry_end, qry_filter, params; - uint32_t cnt = parseSearchRequest(merged, qry_begin, qry_end, + uint32_t cnt = parseSearchRequest(final_query, qry_begin, qry_end, qry_filter, params, log_context); google::protobuf::util::JsonPrintOptions options; @@ -1832,7 +1835,7 @@ void DatabaseAPI::queryUpdate(const SDMS::QueryUpdateRequest &a_request, options.preserve_proto_field_names = true; options.always_print_primitive_fields = true; auto stat = google::protobuf::util::MessageToJsonString( - merged, &query_json, options); + final_query, &query_json, options); if (!stat.ok()) { EXCEPT(1, "Invalid search request"); } From d98341b3f01de0faecfb7b94e53a273f011cb7cc Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 22:02:51 +0000 Subject: [PATCH 08/22] chore: Auto-format JavaScript files with Prettier --- core/database/foxx/api/user_router.js | 1 - 1 file changed, 1 deletion(-) diff --git a/core/database/foxx/api/user_router.js b/core/database/foxx/api/user_router.js index 67be78457..a28cbbd6c 100644 --- a/core/database/foxx/api/user_router.js +++ b/core/database/foxx/api/user_router.js @@ -314,7 +314,6 @@ router router .get("/update", function (req, res) { - let client = null; let result = null; let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; From 930ef28b45cac577f222d1fc63ddd56c179e4226 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 21:42:37 -0500 Subject: [PATCH 09/22] fix: fix user router variable definitions. --- core/database/foxx/api/user_router.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/database/foxx/api/user_router.js b/core/database/foxx/api/user_router.js index a28cbbd6c..797d738a1 100644 --- a/core/database/foxx/api/user_router.js +++ b/core/database/foxx/api/user_router.js @@ -402,14 +402,6 @@ router result = [user.new]; - const { is_admin, max_coll, max_proj, max_sav_qry } = user.new; - - extra_log_info = { - is_admin, - max_coll, - max_proj, - max_sav_qry, - }; }, }); res.send(result); @@ -726,6 +718,7 @@ router router .get("/keys/get", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; + let user = null; try { if (req.queryParams.subject) { if (!g_db.u.exists(req.queryParams.subject)) @@ -734,7 +727,7 @@ router "No such user '" + req.queryParams.subject + "'", ]; - let user = g_db.u.document({ + user = g_db.u.document({ _id: req.queryParams.subject, }); logger.logRequestStarted({ @@ -746,7 +739,7 @@ router description: `Get user public and private keys. ${sub}`, }); } else { - let user = g_lib.getUserFromClientID(req.queryParams.client); + user = g_lib.getUserFromClientID(req.queryParams.client); } if (!user.pub_key || !user.priv_key) { @@ -1037,6 +1030,7 @@ router router .get("/token/get", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; + let user = null; try { const collection_token = UserToken.validateRequestParams(req.queryParams); // TODO: collection type determines logic when mapped vs HA @@ -1049,11 +1043,11 @@ router "No such user '" + req.queryParams.subject + "'", ]; - var user = g_db.u.document({ + user = g_db.u.document({ _id: req.queryParams.subject, }); } else { - var user = g_lib.getUserFromClientID(req.queryParams.client); + user = g_lib.getUserFromClientID(req.queryParams.client); } logger.logRequestStarted({ @@ -1486,7 +1480,9 @@ Note: must delete ALL data records and projects owned by the user being deleted router .get("/delete", function (req, res) { let user_id = null; + let sub = null; try { + sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; logger.logRequestStarted({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], @@ -1611,6 +1607,7 @@ router router .get("/ident/list", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; + let client = null; let extra_log = []; try { client = g_lib.getUserFromClientID(req.queryParams.client); @@ -1681,6 +1678,7 @@ router router .get("/ident/add", function (req, res) { + let client = null; let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; try { logger.logRequestStarted({ From 03c4e9483eabfcbb943f79a9f5ae3e7db2146d48 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 21:43:30 -0500 Subject: [PATCH 10/22] fix: one more error in user_router. --- core/database/foxx/api/user_router.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/database/foxx/api/user_router.js b/core/database/foxx/api/user_router.js index 797d738a1..dbf1fea1d 100644 --- a/core/database/foxx/api/user_router.js +++ b/core/database/foxx/api/user_router.js @@ -1117,6 +1117,7 @@ router router .get("/token/get/access", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; + let user = null; try { if (req.queryParams.subject) { if (!g_db.u.exists(req.queryParams.subject)) @@ -1124,11 +1125,11 @@ router error.ERR_INVALID_PARAM, "No such user '" + req.queryParams.subject + "'", ]; - let user = g_db.u.document({ + user = g_db.u.document({ _id: req.queryParams.subject, }); } else { - let user = g_lib.getUserFromClientID(req.queryParams.client); + user = g_lib.getUserFromClientID(req.queryParams.client); } logger.logRequestStarted({ client: req.queryParams.client, From 0d7377a18e92d9b8f101ba1b2f9e9d9169780c49 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 21:47:38 -0500 Subject: [PATCH 11/22] fix: acl_router variable should be initialized to empty array. --- core/database/foxx/api/acl_router.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/database/foxx/api/acl_router.js b/core/database/foxx/api/acl_router.js index 995b18020..23fad8393 100644 --- a/core/database/foxx/api/acl_router.js +++ b/core/database/foxx/api/acl_router.js @@ -234,7 +234,7 @@ router router .get("/view", function (req, res) { - let rules = null; + let rules = []; try { logger.logRequestStarted({ client: req.queryParams.client, @@ -296,7 +296,7 @@ router .description("View current ACL on an object (data record or collection)"); router .get("/shared/list", function (req, res) { - let result = null; + let result = []; try { const client = g_lib.getUserFromClientID(req.queryParams.client); result = g_lib.getACLOwnersBySubject( From be9662593cbd1f13286a55093422bfd8046e7c52 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 21:52:46 -0500 Subject: [PATCH 12/22] fix: admin_router --- core/database/foxx/api/admin_router.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/database/foxx/api/admin_router.js b/core/database/foxx/api/admin_router.js index 8ef49ce9d..6cadf2b57 100644 --- a/core/database/foxx/api/admin_router.js +++ b/core/database/foxx/api/admin_router.js @@ -58,7 +58,7 @@ router let result = null; try { logger.logRequestStarted({ - client: "N/A", + client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/test", @@ -82,7 +82,7 @@ router time: (t2 - t1) / 1000, }); logger.logRequestSuccess({ - client: "N/A", + client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/test", @@ -92,13 +92,13 @@ router }); } catch (e) { logger.logRequestFailure({ - client: "N/A", + client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/test", status: "Failure", description: "Do perf test", - extra: { execution_time_seconds: (t2 - t1) / 1000 }, + extra: { execution_time_seconds: 0 }, error: e, }); g_lib.handleException(e, res); From a91cccd391c3504f601b10f33c4ba5db3a7cf9d4 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Tue, 24 Feb 2026 21:56:24 -0500 Subject: [PATCH 13/22] fix: config_router --- core/database/foxx/api/config_router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/database/foxx/api/config_router.js b/core/database/foxx/api/config_router.js index 0d94b2eaa..37b4d18e3 100644 --- a/core/database/foxx/api/config_router.js +++ b/core/database/foxx/api/config_router.js @@ -48,7 +48,7 @@ router routePath: basePath + "/msg/daily", status: "Failure", description: "Get message of the day", - extra: (msg.msg || "").substring(0, 10), + extra: ((msg && msg.msg) || "").substring(0, 10), error: e, }); From bb90208ed1e0874a8a95565a538a60206107f566 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 00:32:17 -0500 Subject: [PATCH 14/22] fix: apply fixes to router logic --- core/database/foxx/api/coll_router.js | 3 +- core/database/foxx/api/data_router.js | 56 +++- core/database/foxx/api/group_router.js | 1 - core/database/foxx/api/metrics_router.js | 5 +- core/database/foxx/api/note_router.js | 5 +- core/database/foxx/api/proj_router.js | 326 ++++++++++------------- core/database/foxx/api/query_router.js | 58 ++-- core/database/foxx/api/repo_router.js | 10 +- core/database/foxx/api/tag_router.js | 3 +- core/database/foxx/api/task_router.js | 16 +- core/database/foxx/api/topic_router.js | 21 +- core/database/foxx/api/user_router.js | 17 +- 12 files changed, 263 insertions(+), 258 deletions(-) diff --git a/core/database/foxx/api/coll_router.js b/core/database/foxx/api/coll_router.js index ee37f4739..35efceeb0 100644 --- a/core/database/foxx/api/coll_router.js +++ b/core/database/foxx/api/coll_router.js @@ -43,10 +43,11 @@ router var owner = client, parent_id; + let owner_id = owner._id if (req.body.parent) { parent_id = g_lib.resolveCollID(req.body.parent, client); - var owner_id = g_db.owner.firstExample({ + owner_id = g_db.owner.firstExample({ _from: parent_id, })._to; if (owner_id != client._id) { diff --git a/core/database/foxx/api/data_router.js b/core/database/foxx/api/data_router.js index 078448e5a..8c261c1ce 100644 --- a/core/database/foxx/api/data_router.js +++ b/core/database/foxx/api/data_router.js @@ -515,7 +515,7 @@ function recordUpdate(client, record, result) { perms |= permissions.PERM_WR_REC; } - if (data.locked || !g_lib.hasPermissions(client, data, perms)) throw error.ERR_PERM_DENIED; + if (data.locked || !permissions.hasPermissions(client, data, perms)) throw error.ERR_PERM_DENIED; } var owner_id = g_db.owner.firstExample({ @@ -646,14 +646,16 @@ function recordUpdate(client, record, result) { for (i in data.tags) { tag = data.tags[i]; - if (!(tag in record.tags)) { + if (!record.tags.includes(tag)) { rem_tags.push(tag); } } + + for (i in record.tags) { tag = record.tags[i]; - if (!(tag in data.tags)) { + if (!data.tags.includes(tag)) { add_tags.push(tag); } } @@ -707,7 +709,7 @@ function recordUpdate(client, record, result) { } } - if (record.deps != undefined && (record.deps_add != undefined || record.deps_rem != undefined)) + if (record.deps != undefined && (record.dep_add != undefined || record.dep_rem != undefined)) throw [error.ERR_INVALID_PARAM, "Cannot use both dependency set and add/remove."]; var dep, @@ -1049,7 +1051,7 @@ router httpVerb: "POST", routePath: basePath + "/update/batch", status: "Failure", - description: `Update a batch of existing data record. RecordIDs: ${displayIds}`, + description: `Update a batch of existing data record. RecordIDs: ${displayedIds}`, extra: { count: totalCount, }, @@ -1515,7 +1517,7 @@ router router .get("/dep/graph/get", function (req, res) { - let result = null; + let result = []; try { logger.logRequestStarted({ client: req.queryParams.client, @@ -1541,7 +1543,6 @@ router notes, gen = 0; - result = []; // Get Ancestors //console.log("get ancestors"); @@ -1825,6 +1826,7 @@ router */ router .get("/path", function (req, res) { + let path = null; try { logger.logRequestStarted({ client: req.queryParams.client, @@ -1856,7 +1858,7 @@ router "Can only access data from '" + repo.domain + "' domain", ]; - var path = g_lib.computeDataPath(loc, true); + path = g_lib.computeDataPath(loc, true); res.send({ path: path, }); @@ -2389,6 +2391,16 @@ router router .post("/delete", function (req, res) { var retry = 10; + let ids = []; + logger.logRequestStarted({ + client: req.queryParams.client, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/delete", + status: "Started", + description: `Attempting to delete a total of: ${req.body.ids.length}`, + }); + for (;;) { try { @@ -2417,9 +2429,11 @@ router action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); var i, - id, - ids = []; + id; + // Needs to be reinitialized to an empty array to avoid + // accumulating content from retries + ids = []; for (i in req.body.ids) { id = g_lib.resolveDataCollID(req.body.ids[i], client); ids.push(id); @@ -2430,8 +2444,30 @@ router res.send(result); }, }); + const preview = ids.slice(0, 5).join(", "); + const idSummary = ids.length > 5 ? `${preview}, ...` : preview; + logger.logRequestSuccess({ + client: req.queryParams.client, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/delete", + status: "Success", + description: `Delete data items: ${idSummary}...`, + extra: { count: ids.length }, + }); + break; } catch (e) { + logger.logRequestFailure({ + client: req.queryParams.client, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/delete", + status: "Failure", + description: `Attempting to delete a total of: ${req.body.ids.length}`, + extra: { retry_attempt: retry }, + error: e, + }); if (--retry == 0 || !e.errorNum || e.errorNum != 1200) { g_lib.handleException(e, res); } diff --git a/core/database/foxx/api/group_router.js b/core/database/foxx/api/group_router.js index 6a49bf3b6..9c80ccbb3 100644 --- a/core/database/foxx/api/group_router.js +++ b/core/database/foxx/api/group_router.js @@ -440,7 +440,6 @@ router extra: logExtra, }); } catch (e) { - res.send(groups); logger.logRequestFailure({ client: client?._id, correlationId: req.headers["x-correlation-id"], diff --git a/core/database/foxx/api/metrics_router.js b/core/database/foxx/api/metrics_router.js index 66c762436..76dc05e68 100644 --- a/core/database/foxx/api/metrics_router.js +++ b/core/database/foxx/api/metrics_router.js @@ -73,7 +73,7 @@ router router .get("/msg_count", function (req, res) { let client = null; - let result = null; + let result = []; try { client = g_lib.getUserFromClientID(req.queryParams.client); logger.logRequestStarted({ @@ -147,7 +147,7 @@ router router .get("/users/active", function (req, res) { let client = null; - let cnt = null; + let cnt = {}; try { client = req.queryParams.client ? g_lib.getUserFromClientID(req.queryParams.client) @@ -161,7 +161,6 @@ router description: "Get recently active users from metrics", }); - cnt = {}; var u, r, qryres = g_db diff --git a/core/database/foxx/api/note_router.js b/core/database/foxx/api/note_router.js index cefbcdff7..3fad32187 100644 --- a/core/database/foxx/api/note_router.js +++ b/core/database/foxx/api/note_router.js @@ -180,9 +180,10 @@ router }), old_state = note.state, old_type = note.type, - doc = g_db._document(ne._from), updates = {}; + doc = g_db._document(ne._from); + /* Permissions to update: Currently any admin of the subject and the creator of the annotation may make edits to the annotation. This approach is optimistic in assuming that conflicts will not arise and all parties are ethical. Eventually a mechanism will be put in place to deal with conflicts and @@ -412,7 +413,7 @@ router req.queryParams.id + " Comment ID:" + req.queryParams.comment_idx, - extra: note.new, + extra: note, error: e, }); g_lib.handleException(e, res); diff --git a/core/database/foxx/api/proj_router.js b/core/database/foxx/api/proj_router.js index 47f3d0d53..a04cad6ac 100644 --- a/core/database/foxx/api/proj_router.js +++ b/core/database/foxx/api/proj_router.js @@ -468,11 +468,11 @@ router status: "Failure", description: `Update project information. Project ID: ${req.queryParams.id}`, extra: { - owner: proj.new?.owner, - title: proj.new?.title - ? proj.new?.title.length > 15 - ? proj.new?.title.slice(0, 15) + "…" - : proj.new?.title + owner: proj?.new?.owner, + title: proj?.new?.title + ? proj?.new?.title.length > 15 + ? proj?.new?.title.slice(0, 15) + "…" + : proj?.new?.title : undefined, }, @@ -601,208 +601,166 @@ router router .get("/list", function (req, res) { - logger.logRequestStarted({ - client: req.queryParams.client, - correlationId: req.headers["x-correlation-id"], - httpVerb: "GET", - routePath: basePath + "/list", - status: "Started", - description: `List projects`, - }); - - const client = g_lib.getUserFromClientID(req.queryParams.client); - var qry, - result, - count = - (req.queryParams.as_owner ? 1 : 0) + - (req.queryParams.as_admin ? 1 : 0) + - (req.queryParams.as_member ? 1 : 0); - - if (count) { - var comma = false; - - if (count > 1) qry = "for i in union(("; - else qry = ""; - - if (req.queryParams.as_owner) { - qry += "for i in 1..1 inbound @user owner filter IS_SAME_COLLECTION('p',i)"; - if (count > 1) qry += " return { _id: i._id, title: i.title, owner: i.owner }"; - comma = true; - } - - if (!count || req.queryParams.as_admin) { - qry += - (comma ? "),(" : "") + - "for i in 1..1 inbound @user admin filter IS_SAME_COLLECTION('p',i)"; - if (count > 1) - qry += " return { _id: i._id, title: i.title, owner: i.owner, creator: @user }"; - comma = true; - } - - if (req.queryParams.as_member) { - qry += - (comma ? "),(" : "") + - "for i,e,p in 2..2 inbound @user member, outbound owner filter p.vertices[1].gid == 'members'"; - if (count > 1) qry += " return { _id: i._id, title: i.title, owner: i.owner }"; - } - - if (count > 1) qry += "))"; - } else { - qry = "for i in p"; - } - - qry += " sort i."; - - switch (req.queryParams.sort) { - case g_lib.SORT_ID: - qry += "_id"; - break; - case g_lib.SORT_TITLE: - qry += "title"; - break; - case g_lib.SORT_TIME_CREATE: - qry += "ct"; - break; - case g_lib.SORT_TIME_UPDATE: - qry += "ut"; - break; - default: - qry += "_id"; - break; - } - - if (req.queryParams.sort_rev) qry += " desc"; - - var user_id; - if (req.queryParams.subject) { - permissions.ensureAdminPermUser(client, req.queryParams.subject); - } else user_id = client._id; - - if (req.queryParams.offset != undefined && req.queryParams.count != undefined) { - qry += " limit " + req.queryParams.offset + ", " + req.queryParams.count; - qry += " return { id: i._id, title: i.title, owner: i.owner, creator: i.creator }"; - //console.log("proj list qry:",qry); - result = g_db._query( - qry, - count - ? { - user: user_id, - } - : {}, - {}, - { - fullCount: true, - }, - ); - var tot = result.getExtra().stats.fullCount; - result = result.toArray(); - result.push({ - paging: { - off: req.queryParams.offset, - cnt: req.queryParams.count, - tot: tot, - }, - }); - } else { - qry += " return { id: i._id, title: i.title, owner: i.owner, creator: i.creator }"; - //console.log("proj list qry:",qry); - result = g_db._query( - qry, - count - ? { - user: user_id, - } - : {}, - ); - } - - //res.send( g_db._query( qry, { user: client._id })); - res.send(result); - logger.logRequestSuccess({ - client: req.queryParams.client, - correlationId: req.headers["x-correlation-id"], - httpVerb: "GET", - routePath: basePath + "/list", - status: "Success", - description: `List projects`, - extra: { NumOfProjs: tot }, - }); - }) - .queryParam("client", joi.string().required(), "Client ID") - .queryParam("subject", joi.string().optional(), "Subject (user) ID") - .queryParam("as_owner", joi.bool().optional(), "List projects owned by client/subject") - .queryParam("as_admin", joi.bool().optional(), "List projects administered by client/subject") - .queryParam( - "as_member", - joi.bool().optional(), - "List projects where client is a member/subject", - ) - .queryParam("sort", joi.number().optional(), "Sort field (default = id)") - .queryParam("sort_rev", joi.bool().optional(), "Sort in reverse order") - .queryParam("offset", joi.number().optional(), "Offset") - .queryParam("count", joi.number().optional(), "Count") - .summary("List projects") - .description( - "List projects. If no options are provided, lists all projects associated with client.", - ); - -router - .get("/search", function (req, res) { - let result = null; - let extra_log = null; - const rawQuery = typeof req.queryParams.query === "string" ? req.queryParams.query : ""; - const safeQuerySnippet = - rawQuery.length > 200 ? rawQuery.slice(0, 200) + "…[truncated]" : rawQuery; - try { + let tot = null; + try { logger.logRequestStarted({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", - routePath: basePath + "/search", + routePath: basePath + "/list", status: "Started", - description: `Find all projects that match query: ${safeQuerySnippet}`, + description: `List projects`, }); - g_lib.getUserFromClientID(req.queryParams.client); + const client = g_lib.getUserFromClientID(req.queryParams.client); + var qry, + result, + count = + (req.queryParams.as_owner ? 1 : 0) + + (req.queryParams.as_admin ? 1 : 0) + + (req.queryParams.as_member ? 1 : 0); + + if (count) { + var comma = false; + + if (count > 1) qry = "for i in union(("; + else qry = ""; + + if (req.queryParams.as_owner) { + qry += "for i in 1..1 inbound @user owner filter IS_SAME_COLLECTION('p',i)"; + if (count > 1) qry += " return { _id: i._id, title: i.title, owner: i.owner }"; + comma = true; + } + + if (!count || req.queryParams.as_admin) { + qry += + (comma ? "),(" : "") + + "for i in 1..1 inbound @user admin filter IS_SAME_COLLECTION('p',i)"; + if (count > 1) + qry += " return { _id: i._id, title: i.title, owner: i.owner, creator: @user }"; + comma = true; + } - result = g_db._query(req.queryParams.query, {}); - res.send(result); - extra_log = { - documents: result._documents ? result._documents.slice(0, 10) : [], // first 10 IDs only - countTotal: result?._countTotal, - countQuery: result?._countQuery, - skip: result?._skip, - limit: result?._limit, - cached: result?._cached, - }; + if (req.queryParams.as_member) { + qry += + (comma ? "),(" : "") + + "for i,e,p in 2..2 inbound @user member, outbound owner filter p.vertices[1].gid == 'members'"; + if (count > 1) qry += " return { _id: i._id, title: i.title, owner: i.owner }"; + } + if (count > 1) qry += "))"; + } else { + qry = "for i in p"; + } + + qry += " sort i."; + + switch (req.queryParams.sort) { + case g_lib.SORT_ID: + qry += "_id"; + break; + case g_lib.SORT_TITLE: + qry += "title"; + break; + case g_lib.SORT_TIME_CREATE: + qry += "ct"; + break; + case g_lib.SORT_TIME_UPDATE: + qry += "ut"; + break; + default: + qry += "_id"; + break; + } + + if (req.queryParams.sort_rev) qry += " desc"; + + let user_id; + if (req.queryParams.subject) { + permissions.ensureAdminPermUser(client, req.queryParams.subject); + user_id = req.queryParams.subject; + } else user_id = client._id; + + if (req.queryParams.offset != undefined && req.queryParams.count != undefined) { + qry += " limit " + req.queryParams.offset + ", " + req.queryParams.count; + qry += " return { id: i._id, title: i.title, owner: i.owner, creator: i.creator }"; + //console.log("proj list qry:",qry); + result = g_db._query( + qry, + count + ? { + user: user_id, + } + : {}, + {}, + { + fullCount: true, + }, + ); + tot = result.getExtra().stats.fullCount; + result = result.toArray(); + result.push({ + paging: { + off: req.queryParams.offset, + cnt: req.queryParams.count, + tot: tot, + }, + }); + } else { + qry += " return { id: i._id, title: i.title, owner: i.owner, creator: i.creator }"; + //console.log("proj list qry:",qry); + result = g_db._query( + qry, + count + ? { + user: user_id, + } + : {}, + ); + } + + //res.send( g_db._query( qry, { user: client._id })); + res.send(result); logger.logRequestSuccess({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", - routePath: basePath + "/search", + routePath: basePath + "/list", status: "Success", - description: `Find all projects that match query: ${safeQuerySnippet}`, - extra: extra_log, + description: `List projects`, + extra: { NumOfProjs: tot }, }); } catch (e) { logger.logRequestFailure({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", - routePath: basePath + "/search", + routePath: basePath + "/list", status: "Failure", - description: `Find all projects that match query: ${safeQuerySnippet}`, - extra: extra_log, + description: `List projects`, + extra: { NumOfProjs: tot }, error: e, }); g_lib.handleException(e, res); - } + } }) .queryParam("client", joi.string().required(), "Client ID") - .queryParam("query", joi.string().required(), "Query") - .summary("Find all projects that match query") - .description("Find all projects that match query"); + .queryParam("subject", joi.string().optional(), "Subject (user) ID") + .queryParam("as_owner", joi.bool().optional(), "List projects owned by client/subject") + .queryParam("as_admin", joi.bool().optional(), "List projects administered by client/subject") + .queryParam( + "as_member", + joi.bool().optional(), + "List projects where client is a member/subject", + ) + .queryParam("sort", joi.number().optional(), "Sort field (default = id)") + .queryParam("sort_rev", joi.bool().optional(), "Sort in reverse order") + .queryParam("offset", joi.number().optional(), "Offset") + .queryParam("count", joi.number().optional(), "Count") + .summary("List projects") + .description( + "List projects. If no options are provided, lists all projects associated with client.", + ); router .post("/delete", function (req, res) { @@ -826,7 +784,7 @@ router action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); - var result = g_tasks.taskInitProjDelete(client, req.body.ids); + result = g_tasks.taskInitProjDelete(client, req.body.ids); res.send(result); }, @@ -908,12 +866,12 @@ router extra: { role: role }, }); } catch (e) { - logger.logRequestSuccess({ + logger.logRequestFailure({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/get_role", - status: "Success", + status: "Failure", description: `Get client/subject project role. ID: ${req.queryParams.id}`, extra: { role: role }, error: e, diff --git a/core/database/foxx/api/query_router.js b/core/database/foxx/api/query_router.js index b46badbd0..0cfdd38ce 100644 --- a/core/database/foxx/api/query_router.js +++ b/core/database/foxx/api/query_router.js @@ -19,6 +19,14 @@ router .post("/create", function (req, res) { let result = undefined; try { + logger.logRequestStarted({ + client: req.queryParams.client, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/create", + status: "Started", + description: "Create Query", + }); g_db._executeTransaction({ collections: { read: ["u", "uuid", "accn", "admin"], @@ -26,14 +34,7 @@ router }, action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); - logger.logRequestStarted({ - client: req.queryParams.client, - correlationId: req.headers["x-correlation-id"], - httpVerb: "POST", - routePath: basePath + "/create", - status: "Started", - description: "Create Query", - }); + // Check max number of saved queries if (client.max_sav_qry >= 0) { @@ -82,7 +83,7 @@ router delete qry.qry_end; delete qry.qry_filter; delete qry.params; - delete qry.lmit; + delete qry.limit; result = qry; }, @@ -135,6 +136,15 @@ router .post("/update", function (req, res) { let result = undefined; try { + logger.logRequestStarted({ + client: req.queryParams.client, + correlationId: req.headers["x-correlation-id"], + httpVerb: "POST", + routePath: basePath + "/update", + status: "Started", + description: "Update a saved query", + }); + g_db._executeTransaction({ collections: { read: ["u", "uuid", "accn", "admin"], @@ -142,16 +152,7 @@ router }, action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); - logger.logRequestStarted({ - client: req.queryParams.client, - correlationId: req.headers["x-correlation-id"], - httpVerb: "POST", - routePath: basePath + "/update", - status: "Started", - description: "Update a saved query", - }); - - var qry = g_db.q.document(req.body.id); + var qry = g_db.q.document(req.body.id); if (client._id != qry.owner && !client.is_admin) { throw error.ERR_PERM_DENIED; @@ -189,7 +190,7 @@ router delete qry.qry_end; delete qry.qry_filter; delete qry.params; - delete qry.lmit; + delete qry.limit; result = qry; }, @@ -265,7 +266,7 @@ router delete qry.qry_end; delete qry.qry_filter; delete qry.params; - delete qry.lmit; + delete qry.limit; res.send(qry); logger.logRequestSuccess({ @@ -345,6 +346,7 @@ router extra: req.queryParams.ids[i], }); } + res.send(); } catch (e) { logger.logRequestFailure({ client: req.queryParams.client, @@ -471,7 +473,7 @@ function execQuery(client, mode, published, orig_query) { }, ) .toArray(); - if (!query.params.cols) { + if (!query.params.cols.length) { throw [ error.ERR_PERM_DENIED, "No access to user '" + query.params.owner + "' data/collections.", @@ -505,7 +507,7 @@ function execQuery(client, mode, published, orig_query) { }, ) .toArray(); - if (!query.params.cols) { + if (!query.params.cols.length) { throw [ error.ERR_PERM_DENIED, "No access to project '" + query.params.owner + "'.", @@ -684,7 +686,10 @@ router routePath: basePath + "/exec", status: "Success", description: "Execute specified queries", - extra: results, + extra: { + count: Array.isArray(results) ? results.length : undefined, + query_id: req.queryParams.id, + } }); } catch (e) { logger.logRequestFailure({ @@ -694,7 +699,10 @@ router routePath: basePath + "/exec", status: "Failure", description: "Execute specified queries", - extra: results, + extra: { + count: Array.isArray(results) ? results.length : undefined, + query_id: req.queryParams.id, + }, error: e, }); g_lib.handleException(e, res); diff --git a/core/database/foxx/api/repo_router.js b/core/database/foxx/api/repo_router.js index 27b8b4d94..64a913490 100644 --- a/core/database/foxx/api/repo_router.js +++ b/core/database/foxx/api/repo_router.js @@ -171,9 +171,9 @@ router status: "Failure", description: `View repo server record: ${req.queryParams.id}`, extra: { - type: repo.type, - capacity: repo.capacity, - admins: repo.admins, + type: repo?.type, + capacity: repo?.capacity, + admins: repo?.admins, }, error: e, }); @@ -917,7 +917,7 @@ router permissions.ensureAdminPermRepo(client, req.queryParams.repo); result = getAllocStats(req.queryParams.repo, req.queryParams.subject); res.send(result); - logger.logRequestStarted({ + logger.logRequestSuccess({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", @@ -1211,7 +1211,7 @@ router if (req.queryParams.subject) { if (req.queryParams.subject.startsWith("p/")) { - if (!g_db._exists(subject_id)) + if (!g_db._exists(req.queryParams.subject)) throw [ error.ERR_NOT_FOUND, "Project, " + req.queryParams.subject + ", not found", diff --git a/core/database/foxx/api/tag_router.js b/core/database/foxx/api/tag_router.js index 575d04f3a..7d2e00eb2 100644 --- a/core/database/foxx/api/tag_router.js +++ b/core/database/foxx/api/tag_router.js @@ -19,6 +19,7 @@ router let client = null; let result = null; let tot = null; + let name = null; try { client = req.queryParams.client ? g_lib.getUserFromClientID(req.queryParams.client) @@ -31,7 +32,7 @@ router status: "Started", description: `Search for tags by name (${req.queryParams?.name?.trim()})`, }); - var name = req.queryParams.name.trim(); + name = req.queryParams.name.trim(); if (name.length < 3) throw [error.ERR_INVALID_PARAM, "Input is too short for tag search."]; diff --git a/core/database/foxx/api/task_router.js b/core/database/foxx/api/task_router.js index 292beb922..81cd357ea 100644 --- a/core/database/foxx/api/task_router.js +++ b/core/database/foxx/api/task_router.js @@ -347,6 +347,7 @@ router throw [error.ERR_IN_USE, "Cannot delete task that is still scheduled."]; g_lib.graph.task.remove(req.queryParams.task_id); + res.send(); logger.logRequestSuccess({ client: req?.queryParams?.task_id, correlationId: req.headers["x-correlation-id"], @@ -357,7 +358,6 @@ router extra: req.queryParams.task_id, }); } catch (e) { - g_lib.handleException(e, res); logger.logRequestFailure({ client: req?.queryParams?.task_id, correlationId: req.headers["x-correlation-id"], @@ -368,6 +368,7 @@ router extra: "undefined", error: e, }); + g_lib.handleException(e, res); } }) .queryParam("task_id", joi.string().required(), "Task ID") @@ -380,7 +381,7 @@ router try { const client = g_lib.getUserFromClientID(req.queryParams.client); logger.logRequestStarted({ - client: req?.queryParams?.task_id, + client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/list", @@ -422,7 +423,7 @@ router res.send(result); logger.logRequestSuccess({ - client: req?.queryParams?.task_id, + client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/list", @@ -435,7 +436,7 @@ router }); } catch (e) { logger.logRequestFailure({ - client: req?.queryParams?.task_id, + client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/list", @@ -471,7 +472,7 @@ router try { result = []; logger.logRequestStarted({ - client: req?.queryParams?.task_id, + client: "system", correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/reload", @@ -495,7 +496,7 @@ router res.send(result); logger.logRequestSuccess({ - client: req?.queryParams?.task_id, + client: "system", correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/reload", @@ -505,7 +506,7 @@ router }); } catch (e) { logger.logRequestFailure({ - client: req?.queryParams?.task_id, + client: "system", correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/reload", @@ -549,6 +550,7 @@ router ); }, }); + res.send(); logger.logRequestSuccess({ client: "undefined", correlationId: req.headers["x-correlation-id"], diff --git a/core/database/foxx/api/topic_router.js b/core/database/foxx/api/topic_router.js index b5b531505..ca5ed6456 100644 --- a/core/database/foxx/api/topic_router.js +++ b/core/database/foxx/api/topic_router.js @@ -16,9 +16,7 @@ module.exports = router; router .get("/list/topics", function (req, res) { - let client = req.queryParams.client - ? g_lib.getUserFromClientID(req.queryParams.client) - : undefined; + let client = null; let result = null; try { client = g_lib.getUserFromClientID(req.queryParams.client); @@ -109,11 +107,12 @@ router router .get("/view", function (req, res) { - let client = req.queryParams.client - ? g_lib.getUserFromClientID(req.queryParams.client) - : undefined; + let client = null; let topic_extra = undefined; try { + client = req.queryParams.client + ? g_lib.getUserFromClientID(req.queryParams.client) + : undefined; logger.logRequestStarted({ client: client?._id, correlationId: req.headers["x-correlation-id"], @@ -166,13 +165,14 @@ router router .get("/search", function (req, res) { - let client = req.queryParams.client - ? g_lib.getUserFromClientID(req.queryParams.client) - : undefined; - let result = null; + let client = null; + let result = []; const phrase = req.queryParams.phrase; const shortPhrase = phrase.length > 10 ? phrase.slice(0, 10) + "..." : phrase; try { + client = req.queryParams.client + ? g_lib.getUserFromClientID(req.queryParams.client) + : undefined; logger.logRequestStarted({ client: client?._id, correlationId: req.headers["x-correlation-id"], @@ -194,7 +194,6 @@ router path, op = false; - result = []; if (tokens.length == 0) throw [error.ERR_INVALID_PARAM, "Invalid topic search phrase."]; it = 0; diff --git a/core/database/foxx/api/user_router.js b/core/database/foxx/api/user_router.js index dbf1fea1d..a4687a182 100644 --- a/core/database/foxx/api/user_router.js +++ b/core/database/foxx/api/user_router.js @@ -291,7 +291,7 @@ router routePath: basePath + "/create", status: "Failure", description: "Create new user entry", - extra: user.new.uid, + extra: user?.new?.uid, error: e, }); g_lib.handleException(e, res); @@ -1225,8 +1225,9 @@ router router .get("/view", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; + let client = null; try { - const client = g_lib.getUserFromClientID_noexcept(req.queryParams.client); + client = g_lib.getUserFromClientID_noexcept(req.queryParams.client); logger.logRequestStarted({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], @@ -1325,7 +1326,6 @@ router extra: `uid=${user.uid}, is_admin=${!!client?.is_admin}`, }); //req.queryParams.details ? } catch (e) { - g_lib.handleException(e, res); logger.logRequestFailure({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], @@ -1333,9 +1333,10 @@ router routePath: basePath + "/view", status: "Failure", description: `View User Information. Subject: ${sub}`, - extra: `uid=${user.uid}, is_admin=${!!client?.is_admin}`, + extra: `uid=${user?.uid}, is_admin=${!!client?.is_admin}`, error: e, }); + g_lib.handleException(e, res); } }) .queryParam("client", joi.string().required(), "Client ID") @@ -1713,7 +1714,7 @@ router g_db._exists({ _id: "uuid/" + req.queryParams.ident, }) - ) + ) { logger.logRequestSuccess({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], @@ -1723,8 +1724,8 @@ router description: `Add new linked identity. Subject: ${sub}`, extra: req.queryParams.ident, }); - - return; + return; + } id = g_db.uuid.save( { _key: req.queryParams.ident, @@ -1964,7 +1965,7 @@ router correlationId: req.headers["x-correlation-id"], httpVerb: "GET", routePath: basePath + "/ep/set", - status: "Started", + status: "Success", description: "Set recent end-points", extra: client.eps, }); From 3f6e32986e4a5ff5d7a22062c1547c6a0d5a1ddd Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 00:32:45 -0500 Subject: [PATCH 15/22] fix: remove proj search test --- core/database/foxx/tests/proj_router.test.js | 83 -------------------- 1 file changed, 83 deletions(-) diff --git a/core/database/foxx/tests/proj_router.test.js b/core/database/foxx/tests/proj_router.test.js index 3e854f84c..be4b022c4 100644 --- a/core/database/foxx/tests/proj_router.test.js +++ b/core/database/foxx/tests/proj_router.test.js @@ -367,56 +367,6 @@ describe("unit_proj_router: test project create endpoint", () => { expect(ids).to.include.members(["p/proj1", "p/proj2", "p/proj3"]); }); - it("should search projects using a provided AQL query", () => { - // ------------------------------------------------------------------ - // Arrange - // ------------------------------------------------------------------ - db.u.save({ _key: "search_user", is_admin: false }); - - db.p.save({ - _key: "search_proj1", - title: "Alpha Project", - desc: "First searchable project", - ct: 1, - ut: 1, - owner: "u/search_user", - }); - - db.p.save({ - _key: "search_proj2", - title: "Beta Project", - desc: "Second searchable project", - ct: 1, - ut: 1, - owner: "u/search_user", - }); - - // AQL query passed directly to /search - const aql = "FOR p IN p FILTER p.title LIKE '%Project%' RETURN p._id"; - - const url = - `${proj_base_url}/search` + - `?client=u/search_user` + - `&query=${encodeURIComponent(aql)}`; - - // ------------------------------------------------------------------ - // Act - // ------------------------------------------------------------------ - const response = request.get(url, { - headers: { "x-correlation-id": "test-proj-search" }, - }); - - // ------------------------------------------------------------------ - // Assert - // ------------------------------------------------------------------ - expect(response.status).to.equal(200); - - const body = JSON.parse(response.body); - - expect(body).to.be.an("array"); - expect(body).to.include.members(["p/search_proj1", "p/search_proj2"]); - }); - it("should enqueue a project delete task when client is authorized", () => { // ------------------------------------------------------------------ // Arrange @@ -565,37 +515,4 @@ describe("unit_proj_router: test project create endpoint", () => { expect(body.role).to.equal(1); // member }); - it("should handle malformed AQL for /prj/search without crashing and return an error response", () => { - // ------------------------------------------------------------------ - // Arrange - // ------------------------------------------------------------------ - db.u.save({ _key: "search_user_malformed", is_admin: false }); - - const client = "search_user_malformed"; - - // Intentionally malformed AQL (missing RETURN and invalid syntax) - const malformedBody = { - client, - aql: "FOR p IN p FILTER p.title == @title INVALID_SYNTAX", - bindVars: { - title: "Alpha Project", - }, - }; - - // ------------------------------------------------------------------ - // Act - // ------------------------------------------------------------------ - const response = request.get("/prj/search", malformedBody); - - // ------------------------------------------------------------------ - // Assert - // ------------------------------------------------------------------ - // Expect a 400-series error (bad request / invalid query) and a JSON error payload - expect(response.status).to.be.within(400, 499); - - const body = JSON.parse(response.body); - expect(body).to.have.property("error", true); - expect(body).to.have.property("code"); - expect(body).to.have.property("errorMessage"); - }); }); From d2fa7287f4101b181fab9641f79d924328002bb9 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 00:33:38 -0500 Subject: [PATCH 16/22] fix: remove any reference to unused proj search --- core/server/DatabaseAPI.cpp | 10 ---------- core/server/DatabaseAPI.hpp | 2 -- web/datafed-ws.js | 6 ------ 3 files changed, 18 deletions(-) diff --git a/core/server/DatabaseAPI.cpp b/core/server/DatabaseAPI.cpp index 088486d46..394081ab8 100644 --- a/core/server/DatabaseAPI.cpp +++ b/core/server/DatabaseAPI.cpp @@ -808,16 +808,6 @@ void DatabaseAPI::projGetRole(const SDMS::ProjectGetRoleRequest &a_request, a_reply.set_role((ProjectRole)(unsigned short)obj.getNumber("role")); } -void DatabaseAPI::projSearch(const std::string &a_query, - SDMS::ProjectDataReply &a_reply, - LogContext log_context) { - Value result; - - dbGet("prj/search", {{"query", a_query}}, result, log_context); - - setProjectData(a_reply, result, log_context); -} - void DatabaseAPI::setProjectData(SDMS::ProjectDataReply &a_reply, const Value &a_result, LogContext log_context) { diff --git a/core/server/DatabaseAPI.hpp b/core/server/DatabaseAPI.hpp index f0df10a51..fa9327c50 100644 --- a/core/server/DatabaseAPI.hpp +++ b/core/server/DatabaseAPI.hpp @@ -105,8 +105,6 @@ class DatabaseAPI { SDMS::ProjectDataReply &a_reply, LogContext log_context); void projList(const SDMS::ProjectListRequest &a_request, SDMS::ListingReply &a_reply, LogContext log_context); - void projSearch(const std::string &a_query, SDMS::ProjectDataReply &a_reply, - LogContext log_context); void projGetRole(const SDMS::ProjectGetRoleRequest &a_request, SDMS::ProjectGetRoleReply &a_reply, LogContext log_context); diff --git a/web/datafed-ws.js b/web/datafed-ws.js index 055c107da..e12386156 100755 --- a/web/datafed-ws.js +++ b/web/datafed-ws.js @@ -1020,12 +1020,6 @@ app.get("/api/prj/list", (a_req, a_resp) => { }); }); -app.post("/api/prj/search", (a_req, a_resp) => { - sendMessage("ProjectSearchRequest", a_req.body, a_req, a_resp, function (reply) { - a_resp.send(reply.item ? reply.item : []); - }); -}); - app.get("/api/grp/create", (a_req, a_resp) => { var params = { group: { From d829de2347b95b3e3291f1da97b446b06358cd75 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 05:34:13 +0000 Subject: [PATCH 17/22] chore: Auto-format JavaScript files with Prettier --- core/database/foxx/api/coll_router.js | 2 +- core/database/foxx/api/data_router.js | 21 +++++++++----------- core/database/foxx/api/proj_router.js | 9 +++++---- core/database/foxx/api/query_router.js | 15 +++++++------- core/database/foxx/api/tag_router.js | 2 +- core/database/foxx/api/task_router.js | 4 ++-- core/database/foxx/api/topic_router.js | 6 +++--- core/database/foxx/api/user_router.js | 17 ++++++++-------- core/database/foxx/tests/proj_router.test.js | 1 - 9 files changed, 36 insertions(+), 41 deletions(-) diff --git a/core/database/foxx/api/coll_router.js b/core/database/foxx/api/coll_router.js index 35efceeb0..c5ab01b77 100644 --- a/core/database/foxx/api/coll_router.js +++ b/core/database/foxx/api/coll_router.js @@ -43,7 +43,7 @@ router var owner = client, parent_id; - let owner_id = owner._id + let owner_id = owner._id; if (req.body.parent) { parent_id = g_lib.resolveCollID(req.body.parent, client); diff --git a/core/database/foxx/api/data_router.js b/core/database/foxx/api/data_router.js index 8c261c1ce..457a95831 100644 --- a/core/database/foxx/api/data_router.js +++ b/core/database/foxx/api/data_router.js @@ -515,7 +515,8 @@ function recordUpdate(client, record, result) { perms |= permissions.PERM_WR_REC; } - if (data.locked || !permissions.hasPermissions(client, data, perms)) throw error.ERR_PERM_DENIED; + if (data.locked || !permissions.hasPermissions(client, data, perms)) + throw error.ERR_PERM_DENIED; } var owner_id = g_db.owner.firstExample({ @@ -651,11 +652,9 @@ function recordUpdate(client, record, result) { } } - - for (i in record.tags) { tag = record.tags[i]; - if (!data.tags.includes(tag)) { + if (!data.tags.includes(tag)) { add_tags.push(tag); } } @@ -1826,7 +1825,7 @@ router */ router .get("/path", function (req, res) { - let path = null; + let path = null; try { logger.logRequestStarted({ client: req.queryParams.client, @@ -2391,7 +2390,7 @@ router router .post("/delete", function (req, res) { var retry = 10; - let ids = []; + let ids = []; logger.logRequestStarted({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], @@ -2401,7 +2400,6 @@ router description: `Attempting to delete a total of: ${req.body.ids.length}`, }); - for (;;) { try { g_db._executeTransaction({ @@ -2428,11 +2426,10 @@ router }, action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); - var i, - id; + var i, id; - // Needs to be reinitialized to an empty array to avoid - // accumulating content from retries + // Needs to be reinitialized to an empty array to avoid + // accumulating content from retries ids = []; for (i in req.body.ids) { id = g_lib.resolveDataCollID(req.body.ids[i], client); @@ -2444,7 +2441,7 @@ router res.send(result); }, }); - const preview = ids.slice(0, 5).join(", "); + const preview = ids.slice(0, 5).join(", "); const idSummary = ids.length > 5 ? `${preview}, ...` : preview; logger.logRequestSuccess({ client: req.queryParams.client, diff --git a/core/database/foxx/api/proj_router.js b/core/database/foxx/api/proj_router.js index a04cad6ac..b8ccd3a68 100644 --- a/core/database/foxx/api/proj_router.js +++ b/core/database/foxx/api/proj_router.js @@ -601,8 +601,8 @@ router router .get("/list", function (req, res) { - let tot = null; - try { + let tot = null; + try { logger.logRequestStarted({ client: req.queryParams.client, correlationId: req.headers["x-correlation-id"], @@ -637,7 +637,8 @@ router (comma ? "),(" : "") + "for i in 1..1 inbound @user admin filter IS_SAME_COLLECTION('p',i)"; if (count > 1) - qry += " return { _id: i._id, title: i.title, owner: i.owner, creator: @user }"; + qry += + " return { _id: i._id, title: i.title, owner: i.owner, creator: @user }"; comma = true; } @@ -742,7 +743,7 @@ router error: e, }); g_lib.handleException(e, res); - } + } }) .queryParam("client", joi.string().required(), "Client ID") .queryParam("subject", joi.string().optional(), "Subject (user) ID") diff --git a/core/database/foxx/api/query_router.js b/core/database/foxx/api/query_router.js index 0cfdd38ce..4140675f9 100644 --- a/core/database/foxx/api/query_router.js +++ b/core/database/foxx/api/query_router.js @@ -34,7 +34,6 @@ router }, action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); - // Check max number of saved queries if (client.max_sav_qry >= 0) { @@ -152,7 +151,7 @@ router }, action: function () { const client = g_lib.getUserFromClientID(req.queryParams.client); - var qry = g_db.q.document(req.body.id); + var qry = g_db.q.document(req.body.id); if (client._id != qry.owner && !client.is_admin) { throw error.ERR_PERM_DENIED; @@ -346,7 +345,7 @@ router extra: req.queryParams.ids[i], }); } - res.send(); + res.send(); } catch (e) { logger.logRequestFailure({ client: req.queryParams.client, @@ -473,7 +472,7 @@ function execQuery(client, mode, published, orig_query) { }, ) .toArray(); - if (!query.params.cols.length) { + if (!query.params.cols.length) { throw [ error.ERR_PERM_DENIED, "No access to user '" + query.params.owner + "' data/collections.", @@ -507,7 +506,7 @@ function execQuery(client, mode, published, orig_query) { }, ) .toArray(); - if (!query.params.cols.length) { + if (!query.params.cols.length) { throw [ error.ERR_PERM_DENIED, "No access to project '" + query.params.owner + "'.", @@ -686,10 +685,10 @@ router routePath: basePath + "/exec", status: "Success", description: "Execute specified queries", - extra: { + extra: { count: Array.isArray(results) ? results.length : undefined, query_id: req.queryParams.id, - } + }, }); } catch (e) { logger.logRequestFailure({ @@ -699,7 +698,7 @@ router routePath: basePath + "/exec", status: "Failure", description: "Execute specified queries", - extra: { + extra: { count: Array.isArray(results) ? results.length : undefined, query_id: req.queryParams.id, }, diff --git a/core/database/foxx/api/tag_router.js b/core/database/foxx/api/tag_router.js index 7d2e00eb2..ea30323d9 100644 --- a/core/database/foxx/api/tag_router.js +++ b/core/database/foxx/api/tag_router.js @@ -19,7 +19,7 @@ router let client = null; let result = null; let tot = null; - let name = null; + let name = null; try { client = req.queryParams.client ? g_lib.getUserFromClientID(req.queryParams.client) diff --git a/core/database/foxx/api/task_router.js b/core/database/foxx/api/task_router.js index 81cd357ea..e9ae12dd1 100644 --- a/core/database/foxx/api/task_router.js +++ b/core/database/foxx/api/task_router.js @@ -347,7 +347,7 @@ router throw [error.ERR_IN_USE, "Cannot delete task that is still scheduled."]; g_lib.graph.task.remove(req.queryParams.task_id); - res.send(); + res.send(); logger.logRequestSuccess({ client: req?.queryParams?.task_id, correlationId: req.headers["x-correlation-id"], @@ -550,7 +550,7 @@ router ); }, }); - res.send(); + res.send(); logger.logRequestSuccess({ client: "undefined", correlationId: req.headers["x-correlation-id"], diff --git a/core/database/foxx/api/topic_router.js b/core/database/foxx/api/topic_router.js index ca5ed6456..ef284d292 100644 --- a/core/database/foxx/api/topic_router.js +++ b/core/database/foxx/api/topic_router.js @@ -16,7 +16,7 @@ module.exports = router; router .get("/list/topics", function (req, res) { - let client = null; + let client = null; let result = null; try { client = g_lib.getUserFromClientID(req.queryParams.client); @@ -107,7 +107,7 @@ router router .get("/view", function (req, res) { - let client = null; + let client = null; let topic_extra = undefined; try { client = req.queryParams.client @@ -165,7 +165,7 @@ router router .get("/search", function (req, res) { - let client = null; + let client = null; let result = []; const phrase = req.queryParams.phrase; const shortPhrase = phrase.length > 10 ? phrase.slice(0, 10) + "..." : phrase; diff --git a/core/database/foxx/api/user_router.js b/core/database/foxx/api/user_router.js index a4687a182..4c4b1e031 100644 --- a/core/database/foxx/api/user_router.js +++ b/core/database/foxx/api/user_router.js @@ -401,7 +401,6 @@ router delete user.new.refresh; result = [user.new]; - }, }); res.send(result); @@ -718,7 +717,7 @@ router router .get("/keys/get", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; - let user = null; + let user = null; try { if (req.queryParams.subject) { if (!g_db.u.exists(req.queryParams.subject)) @@ -1030,7 +1029,7 @@ router router .get("/token/get", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; - let user = null; + let user = null; try { const collection_token = UserToken.validateRequestParams(req.queryParams); // TODO: collection type determines logic when mapped vs HA @@ -1117,7 +1116,7 @@ router router .get("/token/get/access", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; - let user = null; + let user = null; try { if (req.queryParams.subject) { if (!g_db.u.exists(req.queryParams.subject)) @@ -1225,7 +1224,7 @@ router router .get("/view", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; - let client = null; + let client = null; try { client = g_lib.getUserFromClientID_noexcept(req.queryParams.client); logger.logRequestStarted({ @@ -1482,7 +1481,7 @@ Note: must delete ALL data records and projects owned by the user being deleted router .get("/delete", function (req, res) { let user_id = null; - let sub = null; + let sub = null; try { sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; logger.logRequestStarted({ @@ -1609,7 +1608,7 @@ router router .get("/ident/list", function (req, res) { let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; - let client = null; + let client = null; let extra_log = []; try { client = g_lib.getUserFromClientID(req.queryParams.client); @@ -1680,7 +1679,7 @@ router router .get("/ident/add", function (req, res) { - let client = null; + let client = null; let sub = req.queryParams.subject ? req.queryParams.subject : req.queryParams.client; try { logger.logRequestStarted({ @@ -1725,7 +1724,7 @@ router extra: req.queryParams.ident, }); return; - } + } id = g_db.uuid.save( { _key: req.queryParams.ident, diff --git a/core/database/foxx/tests/proj_router.test.js b/core/database/foxx/tests/proj_router.test.js index be4b022c4..1d8e46272 100644 --- a/core/database/foxx/tests/proj_router.test.js +++ b/core/database/foxx/tests/proj_router.test.js @@ -514,5 +514,4 @@ describe("unit_proj_router: test project create endpoint", () => { body = JSON.parse(response.body); expect(body.role).to.equal(1); // member }); - }); From 7978503afe04506a4e12f84d06702059abe2f42b Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 03:27:18 -0500 Subject: [PATCH 18/22] fix: revert proto enum names to what they were previously. --- common/proto3/common/enums/dependency_dir.proto | 4 ++-- common/proto3/common/enums/dependency_type.proto | 6 +++--- common/proto3/common/enums/execution_method.proto | 6 +++--- web/static/util.js | 5 +---- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/common/proto3/common/enums/dependency_dir.proto b/common/proto3/common/enums/dependency_dir.proto index 1cd722921..cf3b2f49a 100644 --- a/common/proto3/common/enums/dependency_dir.proto +++ b/common/proto3/common/enums/dependency_dir.proto @@ -5,6 +5,6 @@ package SDMS; option cc_enable_arenas = true; enum DependencyDir { - DEPENDENCY_DIR_IN = 0; - DEPENDENCY_DIR_OUT = 1; + DIR_IN = 0; + DIR_OUT = 1; } diff --git a/common/proto3/common/enums/dependency_type.proto b/common/proto3/common/enums/dependency_type.proto index 795eeff79..16010e48a 100644 --- a/common/proto3/common/enums/dependency_type.proto +++ b/common/proto3/common/enums/dependency_type.proto @@ -5,7 +5,7 @@ package SDMS; option cc_enable_arenas = true; enum DependencyType { - DEPENDENCY_TYPE_IS_DERIVED_FROM = 0; - DEPENDENCY_TYPE_IS_COMPONENT_OF = 1; - DEPENDENCY_TYPE_IS_NEW_VERSION_OF = 2; + DEP_IS_DERIVED_FROM = 0; + DEP_IS_COMPONENT_OF = 1; + DEP_IS_NEW_VERSION_OF = 2; } diff --git a/common/proto3/common/enums/execution_method.proto b/common/proto3/common/enums/execution_method.proto index dff667350..2cd3ef0b7 100644 --- a/common/proto3/common/enums/execution_method.proto +++ b/common/proto3/common/enums/execution_method.proto @@ -5,7 +5,7 @@ package SDMS; option cc_enable_arenas = true; enum ExecutionMethod { - EXECUTION_METHOD_UNSPECIFIED = 0; - EXECUTION_METHOD_DIRECT = 1; - EXECUTION_METHOD_DEFERRED = 2; + EXEC_UNSPECIFIED = 0; + DIRECT = 1; + DEFERRED = 2; } diff --git a/web/static/util.js b/web/static/util.js index ab19b6973..6cff31350 100644 --- a/web/static/util.js +++ b/web/static/util.js @@ -51,14 +51,11 @@ export function getUpdatedValue(a_new_val, a_old_obj, a_new_obj, a_field) { export function getUpdatedValueJSON(a_new_val, a_old_obj, a_new_obj, a_field) { var tmp = a_new_val.trim(), old = a_old_obj[a_field]; - if (old === undefined && tmp.length) { + if ((!old || old === undefined) && tmp.length) { a_new_obj[a_field] = tmp; } else if (tmp.length) { - // Must compare values - have to restringify both b/c formats may differ with same content - // TODO - This should be a deep compare due to possibly inconsistent object arrangement var oldjs = JSON.stringify(JSON.parse(old)), newjs = JSON.stringify(JSON.parse(tmp)); - if (oldjs != newjs) { a_new_obj[a_field] = tmp; } From 699d3ab263539cd1e391bfb2be8253d05a0b8495 Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 03:28:05 -0500 Subject: [PATCH 19/22] fix: add ? checks to variable entries that may not be defined. --- core/database/foxx/api/schema_router.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/core/database/foxx/api/schema_router.js b/core/database/foxx/api/schema_router.js index ee5356207..7dc84fee8 100644 --- a/core/database/foxx/api/schema_router.js +++ b/core/database/foxx/api/schema_router.js @@ -333,11 +333,11 @@ router status: "Failure", description: `Update schema. Schema ID: ${req.queryParams.id}`, extra: { - id: sch_new.id, - own_id: sch_new.own_id, - pub: sch_new.pub, + id: sch_new?.id, + own_id: sch_new?.own_id, + pub: sch_new?.pub, sys: req.body?.sys ?? false, - ver: sch_new.ver, + ver: sch_new?.ver, }, error: e, }); @@ -499,11 +499,11 @@ router status: "Failure", description: `Revise schema. Schema ID: ${req.queryParams.id}`, extra: { - own_id: sch_new.own_id, - own_nm: sch_new.own_nm, - id: sch_new.id, - pub: req.body.pub, - sys: req.body.sys, + own_id: sch_new?.own_id, + own_nm: sch_new?.own_nm, + id: sch_new?.id, + pub: req.body?.pub, + sys: req.body?.sys, }, error: e, }); @@ -608,7 +608,7 @@ router routePath: basePath + "/delete", status: "Failure", description: `Delete schema. Schema ID: ${req.queryParams.id}`, - extra: { deleted: sch_old._id }, + extra: { deleted: sch_old?._id }, }); g_lib.handleException(e, res); } @@ -700,8 +700,8 @@ router status: "Failure", description: `View schema. Schema ID: ${req.queryParams.id}`, extra: { - pub: sch.pub, - sys: sch.sys, + pub: sch?.pub, + sys: sch?.sys, }, }); g_lib.handleException(e, res); From fa2f25148e1f6c0ee8bc69261cc916ae474d7b6d Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 03:28:51 -0500 Subject: [PATCH 20/22] fix: change proto option in core to always_print_enums_as_ints=false --- core/server/DatabaseAPI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/server/DatabaseAPI.cpp b/core/server/DatabaseAPI.cpp index 394081ab8..cdf752e6c 100644 --- a/core/server/DatabaseAPI.cpp +++ b/core/server/DatabaseAPI.cpp @@ -1750,7 +1750,7 @@ void DatabaseAPI::queryCreate(const SDMS::QueryCreateRequest &a_request, google::protobuf::util::JsonPrintOptions options; string query_json; - options.always_print_enums_as_ints = true; + options.always_print_enums_as_ints = false; options.preserve_proto_field_names = true; options.always_print_primitive_fields = true; @@ -1821,7 +1821,7 @@ void DatabaseAPI::queryUpdate(const SDMS::QueryUpdateRequest &a_request, google::protobuf::util::JsonPrintOptions options; string query_json; - options.always_print_enums_as_ints = true; + options.always_print_enums_as_ints = false; options.preserve_proto_field_names = true; options.always_print_primitive_fields = true; auto stat = google::protobuf::util::MessageToJsonString( From 5ff206387988c261008c4d8133cd2a611ce54e9a Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 03:33:12 -0500 Subject: [PATCH 21/22] style: remove commented out ci code block. --- .gitlab/common.yml | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/.gitlab/common.yml b/.gitlab/common.yml index f24889144..588a1990f 100644 --- a/.gitlab/common.yml +++ b/.gitlab/common.yml @@ -161,41 +161,6 @@ reports: dotenv: build.env -#.image_check: -# tags: -# - docker -# script: -# - | -# BRANCH_LOWER=$(echo "$CI_COMMIT_REF_NAME" | tr '[:upper:]' '[:lower:]') -# docker login "${REGISTRY}" -u "${HARBOR_USER}" -p "${HARBOR_DATAFED_GITLAB_CI_REGISTRY_TOKEN}" -# FORCE_BUILD="FALSE" -# set +e -# docker pull --quiet "${REGISTRY}/${PROJECT}/${COMPONENT}-${BRANCH_LOWER}:latest" -# if [ $? -eq 0 ]; then echo "Image exists"; else FORCE_BUILD="TRUE"; fi; -# set -e -# if [ "${BUILD_INTERMEDIATE}" == "TRUE" ] -# then -# set +e -# docker pull --quiet "${REGISTRY}/${PROJECT}/${COMPONENT}-${INTERMEDIATE_LAYER_NAME}-${BRANCH_LOWER}:latest" -# if [ $? -eq 0 ]; then echo "Image exists"; else FORCE_BUILD="TRUE"; fi; -# set -e -# fi -# if [ "$FORCE_BUILD" == "TRUE" ] -# then -# cp .gitlab/build/force_build_${COMPONENT}_image.yml ${COMPONENT}_image.yml -# else -# cp .gitlab/build/build_${COMPONENT}_image.yml ${COMPONENT}_image.yml -# fi -# echo "REGISTRY=${REGISTRY}" >> build.env -# echo "HARBOR_USER=${HARBOR_USER}" >> build.env -# echo "HARBOR_DATAFED_GITLAB_CI_REGISTRY_TOKEN=${HARBOR_DATAFED_GITLAB_CI_REGISTRY_TOKEN}" >> build.env -# sed -i 's/\(HARBOR_USER=.*\)\$/\1$$/g' build.env -# artifacts: -# paths: -# - ${COMPONENT}_image.yml -# reports: -# dotenv: build.env - # The purpose of this anchor is to check that an image has been uploaded correctly # to the registry and if it has not attempt to upload it again. # From d47b7ef3958adfc15e3d1fdfc51e58eec3a7a9eb Mon Sep 17 00:00:00 2001 From: JoshuaSBrown Date: Wed, 25 Feb 2026 06:13:24 -0500 Subject: [PATCH 22/22] fix: adjust query router to handle none numeric mode. --- core/database/foxx/api/query_router.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/database/foxx/api/query_router.js b/core/database/foxx/api/query_router.js index 4140675f9..ff679bb5b 100644 --- a/core/database/foxx/api/query_router.js +++ b/core/database/foxx/api/query_router.js @@ -446,6 +446,10 @@ router .description("List client saved queries"); function execQuery(client, mode, published, orig_query) { + // Make sure we are always dealing with strings. + if (typeof mode === "string" && mode in g_lib) { + mode = g_lib[mode]; + } var col_chk = true, ctxt = client._id; let query = {