Add task and request payload plumbing to CAD dispatcher
- Thread request data through UI bridge and dispatcher events - Add task models, repositories, services, and extension wiring - Include submitted request fields in converted order notes
This commit is contained in:
parent
445a114c1c
commit
b8dd3ef651
@ -77,14 +77,16 @@ switch (_event) do {
|
||||
private _targetGroupID = "";
|
||||
private _note = "";
|
||||
private _priority = "priority";
|
||||
private _request = createHashMap;
|
||||
if (_data isEqualType createHashMap) then {
|
||||
_assigneeGroupID = _data getOrDefault ["assigneeGroupID", ""];
|
||||
_targetGroupID = _data getOrDefault ["targetGroupID", ""];
|
||||
_note = _data getOrDefault ["note", ""];
|
||||
_priority = _data getOrDefault ["priority", "priority"];
|
||||
_request = _data getOrDefault ["request", createHashMap];
|
||||
};
|
||||
|
||||
GVAR(CADUIBridge) call ["requestCreateDispatchOrder", [_assigneeGroupID, _targetGroupID, _note, _priority]];
|
||||
GVAR(CADUIBridge) call ["requestCreateDispatchOrder", [_assigneeGroupID, _targetGroupID, _note, _priority, _request]];
|
||||
};
|
||||
case "cad::supportRequest::submit": {
|
||||
private _type = "";
|
||||
|
||||
@ -218,12 +218,13 @@ GVAR(CADUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
||||
["_assigneeGroupID", "", [""]],
|
||||
["_targetGroupID", "", [""]],
|
||||
["_note", "", [""]],
|
||||
["_priority", "priority", [""]]
|
||||
["_priority", "priority", [""]],
|
||||
["_request", createHashMap, [createHashMap]]
|
||||
];
|
||||
|
||||
if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith { false };
|
||||
|
||||
[SRPC(cad,requestCreateCadDispatchOrder), [getPlayerUID player, _assigneeGroupID, _targetGroupID, _note, _priority]] call CFUNC(serverEvent);
|
||||
[SRPC(cad,requestCreateCadDispatchOrder), [getPlayerUID player, _assigneeGroupID, _targetGroupID, _note, _priority, _request]] call CFUNC(serverEvent);
|
||||
true
|
||||
}],
|
||||
["requestSubmitSupportRequest", compileFinal {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -95,9 +95,26 @@ window.cadDispatcherFormatters = {
|
||||
const groupLabel =
|
||||
request.groupCallsign || request.groupId || "Unknown Group";
|
||||
const summary = (request.summary || "").trim();
|
||||
const fieldDetails =
|
||||
request.fields && typeof request.fields === "object"
|
||||
? Object.entries(request.fields)
|
||||
.map(([fieldID, value]) => {
|
||||
const fieldValue =
|
||||
this.formatRequestFieldValue(value);
|
||||
if (fieldValue === "Not provided") {
|
||||
return "";
|
||||
}
|
||||
|
||||
return summary
|
||||
? `${typeLabel} requested by ${groupLabel}. ${summary}`
|
||||
return `${this.formatRequestFieldLabel(fieldID)} ${fieldValue}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const details = fieldDetails.length
|
||||
? fieldDetails
|
||||
: [summary].filter(Boolean);
|
||||
|
||||
return details.length
|
||||
? `${typeLabel} requested by ${groupLabel}. ${details.join(" | ")}`
|
||||
: `${typeLabel} requested by ${groupLabel}.`;
|
||||
},
|
||||
};
|
||||
|
||||
@ -137,6 +137,12 @@ window.cadDispatcher = {
|
||||
"dispatcherOrderPrioritySelect",
|
||||
).value;
|
||||
const note = document.getElementById("dispatcherOrderNoteInput").value;
|
||||
const sourceRequest = this.convertingRequestId
|
||||
? this.requests.find(
|
||||
(entry) =>
|
||||
(entry.requestId || "") === this.convertingRequestId,
|
||||
) || null
|
||||
: null;
|
||||
|
||||
if (!assigneeGroupID || !targetGroupID) {
|
||||
this.setStatus(
|
||||
@ -165,6 +171,19 @@ window.cadDispatcher = {
|
||||
targetGroupID: targetGroupID,
|
||||
note: note.trim(),
|
||||
priority: priority,
|
||||
request: sourceRequest
|
||||
? {
|
||||
requestId: sourceRequest.requestId || "",
|
||||
type: sourceRequest.type || "",
|
||||
title: sourceRequest.title || "",
|
||||
summary: sourceRequest.summary || "",
|
||||
fields:
|
||||
sourceRequest.fields &&
|
||||
typeof sourceRequest.fields === "object"
|
||||
? sourceRequest.fields
|
||||
: {},
|
||||
}
|
||||
: {},
|
||||
});
|
||||
|
||||
this.closeOrderModal();
|
||||
|
||||
@ -137,8 +137,9 @@ window.cadDispatcherModals = {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestID = this.viewingRequestId;
|
||||
this.closeRequestModal();
|
||||
this.convertRequestToOrder(this.viewingRequestId);
|
||||
this.convertRequestToOrder(requestID);
|
||||
},
|
||||
populateOrderModal(options = {}) {
|
||||
const sortedGroups = this.getSortedGroups();
|
||||
|
||||
@ -45,7 +45,8 @@ call FUNC(initCadStore);
|
||||
["_assigneeGroupID", "", [""]],
|
||||
["_targetGroupID", "", [""]],
|
||||
["_note", "", [""]],
|
||||
["_priority", "priority", [""]]
|
||||
["_priority", "priority", [""]],
|
||||
["_request", createHashMap, [createHashMap]]
|
||||
];
|
||||
|
||||
if (_assigneeGroupID isEqualTo "" || { _targetGroupID isEqualTo "" }) exitWith {
|
||||
@ -57,7 +58,7 @@ call FUNC(initCadStore);
|
||||
"Invalid CAD dispatch order payload.",
|
||||
CRPC(cad,responseCadAssignment),
|
||||
"createDispatchOrder",
|
||||
[_uid, _assigneeGroupID, _targetGroupID, _note, _priority],
|
||||
[_uid, _assigneeGroupID, _targetGroupID, _note, _priority, _request],
|
||||
true,
|
||||
false
|
||||
]];
|
||||
|
||||
@ -243,7 +243,8 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||
["_assigneeGroupID", "", [""]],
|
||||
["_targetGroupID", "", [""]],
|
||||
["_note", "", [""]],
|
||||
["_priority", "priority", [""]]
|
||||
["_priority", "priority", [""]],
|
||||
["_request", createHashMap, [createHashMap]]
|
||||
];
|
||||
|
||||
private _result = createHashMapFromArray [
|
||||
@ -305,6 +306,11 @@ GVAR(AssignmentRepositoryBaseClass) = compileFinal createHashMapFromArray [
|
||||
["targetPosition", +(_targetGroup getOrDefault ["position", []])],
|
||||
["createdByUid", _requesterUid],
|
||||
["createdByName", ["Dispatcher", name _requesterPlayer] select (_requesterPlayer isNotEqualTo objNull)],
|
||||
["requestId", _request getOrDefault ["requestId", ""]],
|
||||
["requestType", _request getOrDefault ["type", ""]],
|
||||
["requestTitle", _request getOrDefault ["title", ""]],
|
||||
["requestSummary", _request getOrDefault ["summary", ""]],
|
||||
["requestFields", +(_request getOrDefault ["fields", createHashMap])],
|
||||
["note", _note],
|
||||
["priority", _finalPriority],
|
||||
["createdAt", serverTime]
|
||||
|
||||
@ -102,18 +102,25 @@ if (_funds > 0) then {
|
||||
["ERROR", format ["Failed to load organization %1 for task %2 funds reward.", _orgID, _taskID]] call EFUNC(common,log);
|
||||
_success = false;
|
||||
} else {
|
||||
private _patch = EGVAR(org,OrgStore) call [
|
||||
"set",
|
||||
private _nextFunds = (_org getOrDefault ["funds", 0]) + _funds;
|
||||
_org set ["funds", _nextFunds];
|
||||
private _updatedOrg = EGVAR(org,OrgStore) call [
|
||||
"callHotOrg",
|
||||
[
|
||||
_orgID,
|
||||
"funds",
|
||||
((_org getOrDefault ["funds", 0]) + _funds),
|
||||
false
|
||||
"org:hot:override",
|
||||
[_orgID, toJSON _org]
|
||||
]
|
||||
];
|
||||
|
||||
[_patch] call _syncOrgPatch;
|
||||
_rewardMessages pushBack format ["$%1 org funds", [_funds] call EFUNC(common,formatNumber)];
|
||||
if (_updatedOrg isEqualTo createHashMap) then {
|
||||
["ERROR", format ["Failed to update organization %1 funds for task %2.", _orgID, _taskID]] call EFUNC(common,log);
|
||||
_success = false;
|
||||
} else {
|
||||
private _patch = createHashMapFromArray [["funds", _nextFunds]];
|
||||
|
||||
[_patch] call _syncOrgPatch;
|
||||
_rewardMessages pushBack format ["$%1 org funds", [_funds] call EFUNC(common,formatNumber)];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -22,11 +22,6 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
["#type", "TaskStore"],
|
||||
["#create", compileFinal {
|
||||
_self set ["participantRegistry", createHashMap];
|
||||
_self set ["defuseRegistry", createHashMap];
|
||||
_self set ["taskOwnershipRegistry", createHashMap];
|
||||
_self set ["taskStatusRegistry", createHashMap];
|
||||
_self set ["completedTaskStatusRegistry", createHashMap];
|
||||
_self set ["taskCatalogRegistry", createHashMap];
|
||||
_self set ["taskEntityRegistries", createHashMapFromArray [
|
||||
["cargo", createHashMap],
|
||||
["hostages", createHashMap],
|
||||
@ -36,6 +31,55 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
["shooters", createHashMap],
|
||||
["targets", createHashMap]
|
||||
]];
|
||||
|
||||
["task:reset", []] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if (
|
||||
!_isSuccess
|
||||
|| { !(_result isEqualType "") }
|
||||
|| { (_result find "Error:") == 0 }
|
||||
) then {
|
||||
["WARNING", "Failed to reset task backend state during task store initialization."] call EFUNC(common,log);
|
||||
};
|
||||
}],
|
||||
["callTaskStateEnvelope", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||
|
||||
private _envelope = createHashMapFromArray [
|
||||
["success", false],
|
||||
["error", ""]
|
||||
];
|
||||
|
||||
if (_function isEqualTo "") exitWith { _envelope };
|
||||
|
||||
[_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||
if !_isSuccess exitWith {
|
||||
_envelope set ["error", format ["Task backend call '%1' failed.", _function]];
|
||||
_envelope
|
||||
};
|
||||
if !(_result isEqualType "") exitWith {
|
||||
_envelope set ["error", format ["Task backend call '%1' returned an invalid response.", _function]];
|
||||
_envelope
|
||||
};
|
||||
if ((_result find "Error:") == 0) exitWith {
|
||||
["ERROR", format ["Task extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
|
||||
_envelope set ["error", _result select [7]];
|
||||
_envelope
|
||||
};
|
||||
|
||||
_envelope set ["success", true];
|
||||
if (_result isNotEqualTo "") then {
|
||||
_envelope set ["data", fromJSON _result];
|
||||
};
|
||||
|
||||
_envelope
|
||||
}],
|
||||
["callTaskState", compileFinal {
|
||||
params [["_function", "", [""]], ["_arguments", [], [[]]], ["_fallback", nil]];
|
||||
|
||||
private _envelope = _self call ["callTaskStateEnvelope", [_function, _arguments]];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith { _fallback };
|
||||
|
||||
_envelope getOrDefault ["data", _fallback]
|
||||
}],
|
||||
["bindTaskOwnership", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_requesterUid", "", [""]]];
|
||||
@ -52,51 +96,46 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
_result
|
||||
};
|
||||
|
||||
if (_requesterUid isEqualTo "") exitWith {
|
||||
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
|
||||
_taskOwnershipRegistry set [_taskID, createHashMapFromArray [
|
||||
["requesterUid", ""],
|
||||
["orgID", "default"]
|
||||
]];
|
||||
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
|
||||
private _orgID = "default";
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", "No requester UID provided. Bound task to default organization."];
|
||||
_result
|
||||
if (_requesterUid isNotEqualTo "") then {
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
||||
if (_actor isEqualTo createHashMap) then {
|
||||
_actor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
|
||||
};
|
||||
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
_orgID = _actor getOrDefault ["organization", ""];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
};
|
||||
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
||||
if (_actor isEqualTo createHashMap) then {
|
||||
_actor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
|
||||
};
|
||||
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _orgID = _actor getOrDefault ["organization", ""];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
|
||||
_taskOwnershipRegistry set [_taskID, createHashMapFromArray [
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgID", _orgID]
|
||||
]];
|
||||
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
|
||||
|
||||
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
private _catalogEntry = +(_taskCatalogRegistry getOrDefault [_taskID, createHashMap]);
|
||||
if (_catalogEntry isNotEqualTo createHashMap) then {
|
||||
_catalogEntry set ["requesterUid", _requesterUid];
|
||||
_catalogEntry set ["orgID", _orgID];
|
||||
_catalogEntry set ["accepted", true];
|
||||
_taskCatalogRegistry set [_taskID, _catalogEntry];
|
||||
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
|
||||
["orgId", _orgID]
|
||||
];
|
||||
private _envelope = _self call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:ownership:bind",
|
||||
[_taskID, toJSON _context]
|
||||
]
|
||||
];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Failed to bind task ownership."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _bindResult = _envelope getOrDefault ["data", createHashMap];
|
||||
_result set ["success", true];
|
||||
_result set ["orgID", _orgID];
|
||||
_result set ["message", _bindResult getOrDefault [
|
||||
"message",
|
||||
["No requester UID provided. Bound task to default organization.", "Task ownership updated."] select (_requesterUid isNotEqualTo "")
|
||||
]];
|
||||
_result set ["orgID", _bindResult getOrDefault ["orgId", _orgID]];
|
||||
_result
|
||||
}],
|
||||
["releaseTaskOwnership", compileFinal {
|
||||
@ -104,45 +143,26 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
|
||||
_taskOwnershipRegistry deleteAt _taskID;
|
||||
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
|
||||
|
||||
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
private _catalogEntry = +(_taskCatalogRegistry getOrDefault [_taskID, createHashMap]);
|
||||
if (_catalogEntry isNotEqualTo createHashMap) then {
|
||||
_catalogEntry set ["requesterUid", ""];
|
||||
_catalogEntry set ["orgID", "default"];
|
||||
_catalogEntry set ["accepted", false];
|
||||
_taskCatalogRegistry set [_taskID, _catalogEntry];
|
||||
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
|
||||
};
|
||||
|
||||
true
|
||||
private _envelope = _self call ["callTaskStateEnvelope", ["task:ownership:release", [_taskID]]];
|
||||
_envelope getOrDefault ["success", false]
|
||||
}],
|
||||
["registerTaskCatalogEntry", compileFinal {
|
||||
params [["_taskID", "", [""]], ["_entry", createHashMap, [createHashMap]]];
|
||||
|
||||
if (_taskID isEqualTo "" || { _entry isEqualTo createHashMap }) exitWith { false };
|
||||
|
||||
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
_taskCatalogRegistry set [_taskID, +_entry];
|
||||
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
|
||||
true
|
||||
private _envelope = _self call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:catalog:upsert",
|
||||
[_taskID, toJSON _entry]
|
||||
]
|
||||
];
|
||||
_envelope getOrDefault ["success", false]
|
||||
}],
|
||||
["getActiveTaskCatalog", compileFinal {
|
||||
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
|
||||
private _entries = [];
|
||||
|
||||
{
|
||||
if ((_taskStatusRegistry getOrDefault [_x, ""]) isNotEqualTo "active") then { continue; };
|
||||
|
||||
private _entry = +_y;
|
||||
_entry set ["taskID", _x];
|
||||
_entry set ["status", "active"];
|
||||
_entries pushBack _entry;
|
||||
} forEach _taskCatalogRegistry;
|
||||
private _entries = _self call ["callTaskState", ["task:catalog:active", [], []]];
|
||||
if !(_entries isEqualType []) exitWith { [] };
|
||||
|
||||
_entries
|
||||
}],
|
||||
@ -160,45 +180,43 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
_result
|
||||
};
|
||||
|
||||
if ((_self call ["getTaskStatus", [_taskID]]) isNotEqualTo "active") exitWith {
|
||||
_result set ["message", "Task is no longer active."];
|
||||
private _actor = EGVAR(actor,Registry) getOrDefault [_requesterUid, createHashMap];
|
||||
if (_actor isEqualTo createHashMap) then {
|
||||
_actor = EGVAR(actor,ActorStore) call ["init", [_requesterUid]];
|
||||
};
|
||||
if (_actor isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", format ["Failed to load actor for %1.", _requesterUid]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
private _entry = +(_taskCatalogRegistry getOrDefault [_taskID, createHashMap]);
|
||||
if (_entry isEqualTo createHashMap) exitWith {
|
||||
_result set ["message", "Task does not exist."];
|
||||
private _orgID = _actor getOrDefault ["organization", ""];
|
||||
if (_orgID isEqualTo "") then { _orgID = "default"; };
|
||||
|
||||
private _context = createHashMapFromArray [
|
||||
["requesterUid", _requesterUid],
|
||||
["orgId", _orgID]
|
||||
];
|
||||
private _envelope = _self call [
|
||||
"callTaskStateEnvelope",
|
||||
[
|
||||
"task:ownership:accept",
|
||||
[_taskID, toJSON _context]
|
||||
]
|
||||
];
|
||||
if !(_envelope getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _envelope getOrDefault ["error", "Unable to accept task."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
|
||||
private _ownership = _taskOwnershipRegistry getOrDefault [_taskID, createHashMap];
|
||||
private _currentRequesterUid = _ownership getOrDefault ["requesterUid", ""];
|
||||
|
||||
if (_currentRequesterUid isNotEqualTo "" && { _currentRequesterUid isNotEqualTo _requesterUid }) exitWith {
|
||||
_result set ["message", "Task has already been accepted."];
|
||||
_result set ["entry", _entry];
|
||||
_result
|
||||
private _acceptResult = _envelope getOrDefault ["data", createHashMap];
|
||||
private _entry = _acceptResult getOrDefault ["entry", createHashMap];
|
||||
if !(_entry isEqualType createHashMap) then {
|
||||
_entry = createHashMap;
|
||||
};
|
||||
|
||||
private _bindResult = _self call ["bindTaskOwnership", [_taskID, _requesterUid]];
|
||||
if !(_bindResult getOrDefault ["success", false]) exitWith {
|
||||
_result set ["message", _bindResult getOrDefault ["message", "Failed to bind task ownership."]];
|
||||
_result
|
||||
};
|
||||
|
||||
private _updatedTaskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
private _updatedEntry = +(_updatedTaskCatalogRegistry getOrDefault [_taskID, _entry]);
|
||||
_updatedEntry set ["accepted", true];
|
||||
_updatedEntry set ["requesterUid", _requesterUid];
|
||||
_updatedEntry set ["orgID", _bindResult getOrDefault ["orgID", "default"]];
|
||||
_updatedTaskCatalogRegistry set [_taskID, _updatedEntry];
|
||||
_self set ["taskCatalogRegistry", _updatedTaskCatalogRegistry];
|
||||
|
||||
_result set ["success", true];
|
||||
_result set ["message", "Task accepted."];
|
||||
_result set ["entry", _updatedEntry];
|
||||
_result set ["message", _acceptResult getOrDefault ["message", "Task accepted."]];
|
||||
_result set ["entry", _entry];
|
||||
_result
|
||||
}],
|
||||
["setTaskStatus", compileFinal {
|
||||
@ -206,42 +224,28 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
|
||||
if (_taskID isEqualTo "" || { _status isEqualTo "" }) exitWith { false };
|
||||
|
||||
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
|
||||
private _completedTaskStatusRegistry = _self getOrDefault ["completedTaskStatusRegistry", createHashMap];
|
||||
_taskStatusRegistry set [_taskID, _status];
|
||||
if (_status in ["succeeded", "failed"]) then {
|
||||
_completedTaskStatusRegistry set [_taskID, _status];
|
||||
} else {
|
||||
_completedTaskStatusRegistry deleteAt _taskID;
|
||||
};
|
||||
_self set ["taskStatusRegistry", _taskStatusRegistry];
|
||||
_self set ["completedTaskStatusRegistry", _completedTaskStatusRegistry];
|
||||
true
|
||||
[(_self call ["callTaskState", ["task:status:set", [_taskID, _status], false]])] params [["_statusResult", false, [false]]];
|
||||
|
||||
_statusResult
|
||||
}],
|
||||
["getTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { "" };
|
||||
|
||||
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
|
||||
private _status = _taskStatusRegistry getOrDefault [_taskID, ""];
|
||||
if (_status isNotEqualTo "") exitWith { _status };
|
||||
private _status = _self call ["callTaskState", ["task:status:get", [_taskID], ""]];
|
||||
if !(_status isEqualType "") exitWith { "" };
|
||||
|
||||
private _completedTaskStatusRegistry = _self getOrDefault ["completedTaskStatusRegistry", createHashMap];
|
||||
_completedTaskStatusRegistry getOrDefault [_taskID, ""]
|
||||
_status
|
||||
}],
|
||||
["clearTaskStatus", compileFinal {
|
||||
params [["_taskID", "", [""]]];
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
|
||||
private _completedTaskStatusRegistry = _self getOrDefault ["completedTaskStatusRegistry", createHashMap];
|
||||
_taskStatusRegistry deleteAt _taskID;
|
||||
_completedTaskStatusRegistry deleteAt _taskID;
|
||||
_self set ["taskStatusRegistry", _taskStatusRegistry];
|
||||
_self set ["completedTaskStatusRegistry", _completedTaskStatusRegistry];
|
||||
true
|
||||
[(_self call ["callTaskState", ["task:status:clear", [_taskID], false]])] params [["_statusResult", false, [false]]];
|
||||
|
||||
_statusResult
|
||||
}],
|
||||
["registerTaskEntity", compileFinal {
|
||||
params [["_registryKey", "", [""]], ["_taskID", "", [""]], ["_entity", objNull, [objNull]]];
|
||||
@ -343,18 +347,23 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { _result };
|
||||
|
||||
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
|
||||
private _ownership = _taskOwnershipRegistry getOrDefault [_taskID, createHashMap];
|
||||
if (_ownership isEqualTo createHashMap) exitWith { _result };
|
||||
private _rewardState = _self call ["callTaskState", ["task:ownership:reward_context", [_taskID], createHashMap]];
|
||||
if (_rewardState isEqualTo createHashMap) exitWith { _result };
|
||||
|
||||
private _requesterUid = _ownership getOrDefault ["requesterUid", ""];
|
||||
private _resolvedOrgID = _ownership getOrDefault ["orgID", ""];
|
||||
private _requesterUid = _rewardState getOrDefault ["requesterUid", ""];
|
||||
private _resolvedOrgID = _rewardState getOrDefault ["orgId", ""];
|
||||
if (_resolvedOrgID isEqualTo "") exitWith { _result };
|
||||
|
||||
private _org = EGVAR(org,OrgStore) call ["loadById", [_resolvedOrgID]];
|
||||
private _memberUids = [];
|
||||
if (_org isNotEqualTo createHashMap) then {
|
||||
_memberUids = EGVAR(org,OrgTreasuryService) call ["resolveOrgMemberUids", [_org, _requesterUid]];
|
||||
private _members = _org getOrDefault ["members", createHashMap];
|
||||
if (_members isEqualType createHashMap) then {
|
||||
_memberUids = keys _members;
|
||||
};
|
||||
if (_requesterUid isNotEqualTo "" && { !(_requesterUid in _memberUids) }) then {
|
||||
_memberUids pushBack _requesterUid;
|
||||
};
|
||||
};
|
||||
|
||||
_result set ["requesterUid", _requesterUid];
|
||||
@ -367,10 +376,8 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { 0 };
|
||||
|
||||
private _defuseRegistry = _self getOrDefault ["defuseRegistry", createHashMap];
|
||||
private _nextCount = 1 + (_defuseRegistry getOrDefault [_taskID, 0]);
|
||||
_defuseRegistry set [_taskID, _nextCount];
|
||||
_self set ["defuseRegistry", _defuseRegistry];
|
||||
private _nextCount = _self call ["callTaskState", ["task:defuse:increment", [_taskID], 0]];
|
||||
if !(_nextCount isEqualType 0) exitWith { 0 };
|
||||
|
||||
_nextCount
|
||||
}],
|
||||
@ -379,8 +386,10 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
|
||||
if (_taskID isEqualTo "") exitWith { 0 };
|
||||
|
||||
private _defuseRegistry = _self getOrDefault ["defuseRegistry", createHashMap];
|
||||
_defuseRegistry getOrDefault [_taskID, 0]
|
||||
private _defuseCount = _self call ["callTaskState", ["task:defuse:get", [_taskID], 0]];
|
||||
if !(_defuseCount isEqualType 0) exitWith { 0 };
|
||||
|
||||
_defuseCount
|
||||
}],
|
||||
["notifyParticipants", compileFinal {
|
||||
params [
|
||||
@ -410,22 +419,9 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
if (_taskID isEqualTo "") exitWith { false };
|
||||
|
||||
private _participantRegistry = _self getOrDefault ["participantRegistry", createHashMap];
|
||||
private _defuseRegistry = _self getOrDefault ["defuseRegistry", createHashMap];
|
||||
private _taskOwnershipRegistry = _self getOrDefault ["taskOwnershipRegistry", createHashMap];
|
||||
private _taskStatusRegistry = _self getOrDefault ["taskStatusRegistry", createHashMap];
|
||||
private _taskCatalogRegistry = _self getOrDefault ["taskCatalogRegistry", createHashMap];
|
||||
|
||||
_participantRegistry deleteAt _taskID;
|
||||
_defuseRegistry deleteAt _taskID;
|
||||
_taskOwnershipRegistry deleteAt _taskID;
|
||||
_taskStatusRegistry deleteAt _taskID;
|
||||
_taskCatalogRegistry deleteAt _taskID;
|
||||
|
||||
_self set ["participantRegistry", _participantRegistry];
|
||||
_self set ["defuseRegistry", _defuseRegistry];
|
||||
_self set ["taskOwnershipRegistry", _taskOwnershipRegistry];
|
||||
_self set ["taskStatusRegistry", _taskStatusRegistry];
|
||||
_self set ["taskCatalogRegistry", _taskCatalogRegistry];
|
||||
_self call ["callTaskState", ["task:clear", [_taskID], false]];
|
||||
_self call ["clearTaskEntities", [_taskID]];
|
||||
true
|
||||
}],
|
||||
@ -532,24 +528,28 @@ GVAR(TaskStore) = createHashMapObject [[
|
||||
if (_org isNotEqualTo createHashMap) then {
|
||||
private _reputation = _org getOrDefault ["reputation", 0];
|
||||
private _nextReputation = round (_reputation + _delta);
|
||||
private _patch = EGVAR(org,OrgStore) call [
|
||||
"set",
|
||||
_org set ["reputation", _nextReputation];
|
||||
private _updatedOrg = EGVAR(org,OrgStore) call [
|
||||
"callHotOrg",
|
||||
[
|
||||
_ownerOrgID,
|
||||
"reputation",
|
||||
_nextReputation,
|
||||
false
|
||||
"org:hot:override",
|
||||
[_ownerOrgID, toJSON _org]
|
||||
]
|
||||
];
|
||||
|
||||
private _memberUids = _rewardContext getOrDefault ["memberUids", []];
|
||||
{
|
||||
private _player = [_x] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
[CRPC(org,responseSyncOrg), [_patch], _player] call CFUNC(targetEvent);
|
||||
} forEach _memberUids;
|
||||
if (_updatedOrg isNotEqualTo createHashMap) then {
|
||||
private _patch = createHashMapFromArray [["reputation", _nextReputation]];
|
||||
private _memberUids = _rewardContext getOrDefault ["memberUids", []];
|
||||
{
|
||||
private _player = [_x] call EFUNC(common,getPlayer);
|
||||
if (isNull _player) then { continue; };
|
||||
[CRPC(org,responseSyncOrg), [_patch], _player] call CFUNC(targetEvent);
|
||||
} forEach _memberUids;
|
||||
|
||||
_orgIds = [_ownerOrgID];
|
||||
_orgIds = [_ownerOrgID];
|
||||
} else {
|
||||
["ERROR", format ["Failed to update organization %1 reputation for task %2.", _ownerOrgID, _taskID]] call EFUNC(common,log);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ mod log;
|
||||
pub mod org;
|
||||
pub mod redis;
|
||||
pub mod store;
|
||||
pub mod task;
|
||||
pub mod terrain;
|
||||
pub mod transport;
|
||||
pub mod v_garage;
|
||||
@ -87,6 +88,7 @@ fn init() -> Extension {
|
||||
.group("locker", locker::group())
|
||||
.group("org", org::group())
|
||||
.group("store", store::group())
|
||||
.group("task", task::group())
|
||||
.group("terrain", terrain::group())
|
||||
.group("transport", transport::group())
|
||||
.group(
|
||||
|
||||
123
arma/server/extension/src/task.rs
Normal file
123
arma/server/extension/src/task.rs
Normal file
@ -0,0 +1,123 @@
|
||||
//! Task hot-state operations for the Arma 3 server extension.
|
||||
//!
|
||||
//! The extension owns portable task metadata while SQF keeps Arma-only runtime
|
||||
//! state such as entity references and participant tracking.
|
||||
|
||||
use arma_rs::Group;
|
||||
use forge_repositories::InMemoryTaskRepository;
|
||||
use forge_services::TaskStateService;
|
||||
use serde::Serialize;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
static TASK_SERVICE: LazyLock<TaskStateService<InMemoryTaskRepository>> =
|
||||
LazyLock::new(|| TaskStateService::new(InMemoryTaskRepository::new()));
|
||||
|
||||
pub fn group() -> Group {
|
||||
Group::new()
|
||||
.command("reset", reset)
|
||||
.group(
|
||||
"catalog",
|
||||
Group::new()
|
||||
.command("active", list_active_catalog)
|
||||
.command("get", get_catalog_entry)
|
||||
.command("upsert", upsert_catalog_entry)
|
||||
.command("delete", delete_catalog_entry),
|
||||
)
|
||||
.group(
|
||||
"ownership",
|
||||
Group::new()
|
||||
.command("bind", bind_ownership)
|
||||
.command("release", release_ownership)
|
||||
.command("accept", accept_task)
|
||||
.command("reward_context", reward_context),
|
||||
)
|
||||
.group(
|
||||
"status",
|
||||
Group::new()
|
||||
.command("set", set_status)
|
||||
.command("get", get_status)
|
||||
.command("clear", clear_status),
|
||||
)
|
||||
.group(
|
||||
"defuse",
|
||||
Group::new()
|
||||
.command("increment", increment_defuse_count)
|
||||
.command("get", get_defuse_count),
|
||||
)
|
||||
.command("clear", clear_task)
|
||||
}
|
||||
|
||||
pub(crate) fn list_active_catalog() -> String {
|
||||
serialize_json(TASK_SERVICE.list_active_catalog())
|
||||
}
|
||||
|
||||
pub(crate) fn reset() -> String {
|
||||
serialize_json(TASK_SERVICE.reset())
|
||||
}
|
||||
|
||||
pub(crate) fn get_catalog_entry(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.get_catalog_entry(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn upsert_catalog_entry(entry_id: String, json_data: String) -> String {
|
||||
serialize_json(TASK_SERVICE.upsert_catalog_entry(entry_id, json_data))
|
||||
}
|
||||
|
||||
pub(crate) fn delete_catalog_entry(entry_id: String) -> String {
|
||||
serialize_ok(TASK_SERVICE.delete_catalog_entry(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn bind_ownership(entry_id: String, json_data: String) -> String {
|
||||
serialize_json(TASK_SERVICE.bind_ownership(entry_id, json_data))
|
||||
}
|
||||
|
||||
pub(crate) fn release_ownership(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.release_ownership(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn accept_task(entry_id: String, json_data: String) -> String {
|
||||
serialize_json(TASK_SERVICE.accept_task(entry_id, json_data))
|
||||
}
|
||||
|
||||
pub(crate) fn reward_context(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.get_reward_context(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn set_status(entry_id: String, status: String) -> String {
|
||||
serialize_json(TASK_SERVICE.set_status(entry_id, status))
|
||||
}
|
||||
|
||||
pub(crate) fn get_status(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.get_status(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_status(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.clear_status(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn increment_defuse_count(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.increment_defuse_count(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn get_defuse_count(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.get_defuse_count(entry_id))
|
||||
}
|
||||
|
||||
pub(crate) fn clear_task(entry_id: String) -> String {
|
||||
serialize_json(TASK_SERVICE.clear_task(entry_id))
|
||||
}
|
||||
|
||||
fn serialize_json<T: Serialize>(result: Result<T, String>) -> String {
|
||||
match result {
|
||||
Ok(value) => serde_json::to_string(&value)
|
||||
.unwrap_or_else(|error| format!("Error: Failed to serialize task state: {error}")),
|
||||
Err(error) => format!("Error: {error}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_ok(result: Result<(), String>) -> String {
|
||||
match result {
|
||||
Ok(()) => "true".to_string(),
|
||||
Err(error) => format!("Error: {error}"),
|
||||
}
|
||||
}
|
||||
@ -12,10 +12,11 @@ serde_json = { workspace = true, optional = true }
|
||||
forge-shared = { path = "../shared" }
|
||||
|
||||
[features]
|
||||
default = ["actor", "bank", "member", "org"]
|
||||
default = ["actor", "bank", "member", "org", "task"]
|
||||
|
||||
actor = ["arma-rs", "serde_json"]
|
||||
bank = ["arma-rs", "serde_json"]
|
||||
member = ["arma-rs", "serde_json"]
|
||||
org = ["arma-rs", "serde_json"]
|
||||
task = ["arma-rs", "serde_json"]
|
||||
arma-rs = ["arma-rs/serde_json"]
|
||||
|
||||
@ -58,6 +58,16 @@ pub struct CadDispatchOrderContextSeed {
|
||||
#[serde(default)]
|
||||
pub created_by_name: String,
|
||||
#[serde(default)]
|
||||
pub request_id: String,
|
||||
#[serde(default)]
|
||||
pub request_type: String,
|
||||
#[serde(default)]
|
||||
pub request_title: String,
|
||||
#[serde(default)]
|
||||
pub request_summary: String,
|
||||
#[serde(default)]
|
||||
pub request_fields: CadRecord,
|
||||
#[serde(default)]
|
||||
pub note: String,
|
||||
#[serde(default)]
|
||||
pub priority: String,
|
||||
|
||||
@ -5,6 +5,7 @@ pub mod garage;
|
||||
pub mod locker;
|
||||
pub mod org;
|
||||
pub mod store;
|
||||
pub mod task;
|
||||
pub mod v_garage;
|
||||
pub mod v_locker;
|
||||
|
||||
@ -31,5 +32,8 @@ pub use store::{
|
||||
StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed,
|
||||
StoreGrantedItem, StoreGrantedVehicle,
|
||||
};
|
||||
pub use task::{
|
||||
TaskJsonMap, TaskOwnershipContext, TaskOwnershipMutationResult, TaskRecord, TaskRewardContext,
|
||||
};
|
||||
pub use v_garage::{VGarage, VehicleCategory};
|
||||
pub use v_locker::{EquipmentCategory, VLocker};
|
||||
|
||||
57
lib/models/src/task.rs
Normal file
57
lib/models/src/task.rs
Normal file
@ -0,0 +1,57 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
pub type TaskJsonMap = Map<String, Value>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct TaskRecord {
|
||||
pub fields: TaskJsonMap,
|
||||
}
|
||||
|
||||
impl TaskRecord {
|
||||
pub fn into_value(self) -> Value {
|
||||
Value::Object(self.fields)
|
||||
}
|
||||
|
||||
pub fn to_value(&self) -> Value {
|
||||
Value::Object(self.fields.clone())
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.fields.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TaskOwnershipContext {
|
||||
#[serde(default)]
|
||||
pub requester_uid: String,
|
||||
#[serde(default)]
|
||||
pub org_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TaskOwnershipMutationResult {
|
||||
#[serde(default)]
|
||||
pub task_id: String,
|
||||
#[serde(default)]
|
||||
pub requester_uid: String,
|
||||
#[serde(default)]
|
||||
pub org_id: String,
|
||||
#[serde(default)]
|
||||
pub entry: Value,
|
||||
#[serde(default)]
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TaskRewardContext {
|
||||
#[serde(default)]
|
||||
pub requester_uid: String,
|
||||
#[serde(default)]
|
||||
pub org_id: String,
|
||||
}
|
||||
@ -4,6 +4,7 @@ pub mod cad;
|
||||
pub mod garage;
|
||||
pub mod locker;
|
||||
pub mod org;
|
||||
pub mod task;
|
||||
pub mod v_garage;
|
||||
pub mod v_locker;
|
||||
|
||||
@ -19,6 +20,7 @@ pub use locker::{
|
||||
InMemoryLockerHotRepository, LockerHotRepository, LockerRepository, RedisLockerRepository,
|
||||
};
|
||||
pub use org::{InMemoryOrgHotRepository, OrgHotRepository, OrgRepository, RedisOrgRepository};
|
||||
pub use task::{InMemoryTaskRepository, TaskRepository};
|
||||
pub use v_garage::{
|
||||
InMemoryVGarageHotRepository, RedisVGarageRepository, VGarageHotRepository, VGarageRepository,
|
||||
};
|
||||
|
||||
204
lib/repositories/src/task.rs
Normal file
204
lib/repositories/src/task.rs
Normal file
@ -0,0 +1,204 @@
|
||||
use forge_models::{TaskOwnershipContext, TaskRecord};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
pub trait TaskRepository: Send + Sync {
|
||||
fn reset(&self) -> Result<(), String>;
|
||||
|
||||
fn list_catalog(&self) -> Result<HashMap<String, TaskRecord>, String>;
|
||||
fn get_catalog_entry(&self, id: &str) -> Result<Option<TaskRecord>, String>;
|
||||
fn save_catalog_entry(&self, id: String, entry: TaskRecord) -> Result<(), String>;
|
||||
fn delete_catalog_entry(&self, id: &str) -> Result<(), String>;
|
||||
|
||||
fn get_ownership(&self, id: &str) -> Result<Option<TaskOwnershipContext>, String>;
|
||||
fn save_ownership(&self, id: String, ownership: TaskOwnershipContext) -> Result<(), String>;
|
||||
fn delete_ownership(&self, id: &str) -> Result<(), String>;
|
||||
|
||||
fn list_active_statuses(&self) -> Result<HashMap<String, String>, String>;
|
||||
fn get_active_status(&self, id: &str) -> Result<Option<String>, String>;
|
||||
fn set_active_status(&self, id: String, status: String) -> Result<(), String>;
|
||||
fn delete_active_status(&self, id: &str) -> Result<(), String>;
|
||||
|
||||
fn get_completed_status(&self, id: &str) -> Result<Option<String>, String>;
|
||||
fn set_completed_status(&self, id: String, status: String) -> Result<(), String>;
|
||||
fn delete_completed_status(&self, id: &str) -> Result<(), String>;
|
||||
|
||||
fn increment_defuse_count(&self, id: &str) -> Result<u64, String>;
|
||||
fn get_defuse_count(&self, id: &str) -> Result<u64, String>;
|
||||
fn clear_defuse_count(&self, id: &str) -> Result<(), String>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct TaskState {
|
||||
catalog: HashMap<String, TaskRecord>,
|
||||
ownership: HashMap<String, TaskOwnershipContext>,
|
||||
active_statuses: HashMap<String, String>,
|
||||
completed_statuses: HashMap<String, String>,
|
||||
defuse_counts: HashMap<String, u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct InMemoryTaskRepository {
|
||||
state: Arc<RwLock<TaskState>>,
|
||||
}
|
||||
|
||||
impl InMemoryTaskRepository {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskRepository for InMemoryTaskRepository {
|
||||
fn reset(&self) -> Result<(), String> {
|
||||
let mut state = self
|
||||
.state
|
||||
.write()
|
||||
.map_err(|_| "Task state lock poisoned.".to_string())?;
|
||||
state.catalog.clear();
|
||||
state.ownership.clear();
|
||||
state.active_statuses.clear();
|
||||
state.completed_statuses.clear();
|
||||
state.defuse_counts.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_catalog(&self) -> Result<HashMap<String, TaskRecord>, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.catalog.clone())
|
||||
.map_err(|_| "Task catalog state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn get_catalog_entry(&self, id: &str) -> Result<Option<TaskRecord>, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.catalog.get(id).cloned())
|
||||
.map_err(|_| "Task catalog state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn save_catalog_entry(&self, id: String, entry: TaskRecord) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task catalog state lock poisoned.".to_string())?
|
||||
.catalog
|
||||
.insert(id, entry);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_catalog_entry(&self, id: &str) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task catalog state lock poisoned.".to_string())?
|
||||
.catalog
|
||||
.remove(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_ownership(&self, id: &str) -> Result<Option<TaskOwnershipContext>, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.ownership.get(id).cloned())
|
||||
.map_err(|_| "Task ownership state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn save_ownership(&self, id: String, ownership: TaskOwnershipContext) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task ownership state lock poisoned.".to_string())?
|
||||
.ownership
|
||||
.insert(id, ownership);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_ownership(&self, id: &str) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task ownership state lock poisoned.".to_string())?
|
||||
.ownership
|
||||
.remove(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn list_active_statuses(&self) -> Result<HashMap<String, String>, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.active_statuses.clone())
|
||||
.map_err(|_| "Task status state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn get_active_status(&self, id: &str) -> Result<Option<String>, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.active_statuses.get(id).cloned())
|
||||
.map_err(|_| "Task status state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn set_active_status(&self, id: String, status: String) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task status state lock poisoned.".to_string())?
|
||||
.active_statuses
|
||||
.insert(id, status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_active_status(&self, id: &str) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task status state lock poisoned.".to_string())?
|
||||
.active_statuses
|
||||
.remove(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_completed_status(&self, id: &str) -> Result<Option<String>, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.completed_statuses.get(id).cloned())
|
||||
.map_err(|_| "Task completed status state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn set_completed_status(&self, id: String, status: String) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task completed status state lock poisoned.".to_string())?
|
||||
.completed_statuses
|
||||
.insert(id, status);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_completed_status(&self, id: &str) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task completed status state lock poisoned.".to_string())?
|
||||
.completed_statuses
|
||||
.remove(id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn increment_defuse_count(&self, id: &str) -> Result<u64, String> {
|
||||
let mut state = self
|
||||
.state
|
||||
.write()
|
||||
.map_err(|_| "Task defuse state lock poisoned.".to_string())?;
|
||||
let next_count = 1 + state.defuse_counts.get(id).copied().unwrap_or_default();
|
||||
state.defuse_counts.insert(id.to_string(), next_count);
|
||||
Ok(next_count)
|
||||
}
|
||||
|
||||
fn get_defuse_count(&self, id: &str) -> Result<u64, String> {
|
||||
self.state
|
||||
.read()
|
||||
.map(|state| state.defuse_counts.get(id).copied().unwrap_or_default())
|
||||
.map_err(|_| "Task defuse state lock poisoned.".to_string())
|
||||
}
|
||||
|
||||
fn clear_defuse_count(&self, id: &str) -> Result<(), String> {
|
||||
self.state
|
||||
.write()
|
||||
.map_err(|_| "Task defuse state lock poisoned.".to_string())?
|
||||
.defuse_counts
|
||||
.remove(id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -253,6 +253,26 @@ impl<R: CadRepository> CadStateService<R> {
|
||||
"createdByName".to_string(),
|
||||
Value::String(created_by_name.clone()),
|
||||
),
|
||||
(
|
||||
"sourceRequestId".to_string(),
|
||||
Value::String(seed.request_id.clone()),
|
||||
),
|
||||
(
|
||||
"sourceRequestType".to_string(),
|
||||
Value::String(seed.request_type.clone()),
|
||||
),
|
||||
(
|
||||
"sourceRequestTitle".to_string(),
|
||||
Value::String(seed.request_title.clone()),
|
||||
),
|
||||
(
|
||||
"sourceRequestSummary".to_string(),
|
||||
Value::String(seed.request_summary.clone()),
|
||||
),
|
||||
(
|
||||
"sourceRequestFields".to_string(),
|
||||
seed.request_fields.to_value(),
|
||||
),
|
||||
("createdAt".to_string(), Value::from(seed.created_at)),
|
||||
("note".to_string(), Value::String(seed.note.clone())),
|
||||
("isDispatchOrder".to_string(), Value::Bool(true)),
|
||||
@ -755,8 +775,11 @@ impl<R: CadRepository> CadStateService<R> {
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
),
|
||||
"logreq" => format!(
|
||||
"Category {} | Delivery {} | Location {}",
|
||||
"Category {} | Requested {} | Quantity {} | Delivery {} | Location {}",
|
||||
Self::string_field(fields, "category").unwrap_or_else(|| "mixed".to_string()),
|
||||
Self::string_field(fields, "requested_items")
|
||||
.unwrap_or_else(|| "unspecified".to_string()),
|
||||
Self::string_field(fields, "quantity").unwrap_or_else(|| "unspecified".to_string()),
|
||||
Self::string_field(fields, "delivery_method")
|
||||
.unwrap_or_else(|| "dispatch discretion".to_string()),
|
||||
Self::string_field(fields, "delivery_location")
|
||||
@ -1031,6 +1054,61 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_order_from_context_persists_source_request_metadata() {
|
||||
let repository = InMemoryCadRepository::new();
|
||||
let service = CadStateService::new(repository.clone());
|
||||
|
||||
let result = service
|
||||
.create_order_from_context(
|
||||
r#"{
|
||||
"assigneeGroupId": "bravo",
|
||||
"assigneeGroupCallsign": "Bravo 1-1",
|
||||
"targetGroupId": "alpha",
|
||||
"targetGroupCallsign": "Alpha 1-1",
|
||||
"targetPosition": [1000, 2000, 0],
|
||||
"createdByUid": "dispatcher-1",
|
||||
"createdByName": "Dispatch",
|
||||
"requestId": "cad-request:7",
|
||||
"requestType": "logreq",
|
||||
"requestTitle": "LOGREQ | Alpha 1-1",
|
||||
"requestSummary": "Category ammo | Requested MX rifle ammo",
|
||||
"requestFields": {
|
||||
"category": "ammo",
|
||||
"requested_items": "MX rifle ammo",
|
||||
"quantity": "4 crates"
|
||||
},
|
||||
"note": "LOGREQ requested by Alpha 1-1. Requested Items MX rifle ammo | Quantity 4 crates",
|
||||
"priority": "priority",
|
||||
"createdAt": 123.45
|
||||
}"#
|
||||
.to_string(),
|
||||
)
|
||||
.expect("create order from context should succeed");
|
||||
|
||||
let stored_order = repository
|
||||
.get_order(&result.task_id)
|
||||
.expect("get order should succeed")
|
||||
.expect("order should exist");
|
||||
|
||||
assert_eq!(
|
||||
stored_order.fields.get("sourceRequestId"),
|
||||
Some(&Value::String("cad-request:7".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
stored_order.fields.get("sourceRequestType"),
|
||||
Some(&Value::String("logreq".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
stored_order.fields.get("sourceRequestFields"),
|
||||
Some(&serde_json::json!({
|
||||
"category": "ammo",
|
||||
"requested_items": "MX rifle ammo",
|
||||
"quantity": "4 crates"
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decline_assignment_returns_record_and_removes_state() {
|
||||
let repository = InMemoryCadRepository::new();
|
||||
|
||||
@ -5,6 +5,7 @@ pub mod garage;
|
||||
pub mod locker;
|
||||
pub mod org;
|
||||
pub mod store;
|
||||
pub mod task;
|
||||
pub mod v_garage;
|
||||
pub mod v_locker;
|
||||
|
||||
@ -15,5 +16,6 @@ pub use garage::{GarageHotStateService, GarageService};
|
||||
pub use locker::{LockerHotStateService, LockerService};
|
||||
pub use org::{OrgHotStateService, OrgService};
|
||||
pub use store::StoreService;
|
||||
pub use task::TaskStateService;
|
||||
pub use v_garage::{VGarageHotStateService, VGarageService};
|
||||
pub use v_locker::{VLockerHotStateService, VLockerService};
|
||||
|
||||
379
lib/services/src/task.rs
Normal file
379
lib/services/src/task.rs
Normal file
@ -0,0 +1,379 @@
|
||||
use forge_models::{
|
||||
TaskOwnershipContext, TaskOwnershipMutationResult, TaskRecord, TaskRewardContext,
|
||||
};
|
||||
use forge_repositories::TaskRepository;
|
||||
use serde_json::Value;
|
||||
|
||||
pub struct TaskStateService<R: TaskRepository> {
|
||||
repository: R,
|
||||
}
|
||||
|
||||
impl<R: TaskRepository> TaskStateService<R> {
|
||||
pub fn new(repository: R) -> Self {
|
||||
Self { repository }
|
||||
}
|
||||
|
||||
pub fn reset(&self) -> Result<bool, String> {
|
||||
self.repository.reset()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn upsert_catalog_entry(
|
||||
&self,
|
||||
entry_id: String,
|
||||
json_data: String,
|
||||
) -> Result<TaskRecord, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
let mut entry = Self::parse_record(&json_data)?;
|
||||
Self::normalize_catalog_entry(&mut entry, &entry_id);
|
||||
self.repository
|
||||
.save_catalog_entry(entry_id, entry.clone())?;
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
pub fn get_catalog_entry(&self, entry_id: String) -> Result<Option<Value>, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
self.repository
|
||||
.get_catalog_entry(&entry_id)
|
||||
.map(|entry| entry.map(TaskRecord::into_value))
|
||||
}
|
||||
|
||||
pub fn delete_catalog_entry(&self, entry_id: String) -> Result<(), String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
self.repository.delete_catalog_entry(&entry_id)
|
||||
}
|
||||
|
||||
pub fn list_active_catalog(&self) -> Result<Vec<Value>, String> {
|
||||
let catalog = self.repository.list_catalog()?;
|
||||
let active_statuses = self.repository.list_active_statuses()?;
|
||||
let mut active_entries = Vec::new();
|
||||
|
||||
for (task_id, status) in active_statuses {
|
||||
if status != "active" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(entry) = catalog.get(&task_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut entry = entry.fields.clone();
|
||||
entry.insert("taskId".to_string(), Value::String(task_id.clone()));
|
||||
entry.insert("taskID".to_string(), Value::String(task_id));
|
||||
entry.insert("status".to_string(), Value::String(status));
|
||||
active_entries.push(Value::Object(entry));
|
||||
}
|
||||
|
||||
Ok(active_entries)
|
||||
}
|
||||
|
||||
pub fn bind_ownership(
|
||||
&self,
|
||||
entry_id: String,
|
||||
json_data: String,
|
||||
) -> Result<TaskOwnershipMutationResult, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
let mut ownership = Self::parse_ownership_context(&json_data)?;
|
||||
if ownership.org_id.trim().is_empty() {
|
||||
ownership.org_id = "default".to_string();
|
||||
}
|
||||
|
||||
self.repository
|
||||
.save_ownership(entry_id.clone(), ownership.clone())?;
|
||||
let entry = self.patch_catalog_ownership(
|
||||
&entry_id,
|
||||
true,
|
||||
&ownership.requester_uid,
|
||||
&ownership.org_id,
|
||||
)?;
|
||||
|
||||
Ok(TaskOwnershipMutationResult {
|
||||
task_id: entry_id,
|
||||
requester_uid: ownership.requester_uid,
|
||||
org_id: ownership.org_id,
|
||||
entry,
|
||||
message: "Task ownership updated.".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn release_ownership(
|
||||
&self,
|
||||
entry_id: String,
|
||||
) -> Result<TaskOwnershipMutationResult, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
let ownership = self
|
||||
.repository
|
||||
.get_ownership(&entry_id)?
|
||||
.unwrap_or_default();
|
||||
self.repository.delete_ownership(&entry_id)?;
|
||||
let entry = self.patch_catalog_ownership(&entry_id, false, "", "default")?;
|
||||
|
||||
Ok(TaskOwnershipMutationResult {
|
||||
task_id: entry_id,
|
||||
requester_uid: ownership.requester_uid,
|
||||
org_id: ownership.org_id,
|
||||
entry,
|
||||
message: "Task ownership released.".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn accept_task(
|
||||
&self,
|
||||
entry_id: String,
|
||||
json_data: String,
|
||||
) -> Result<TaskOwnershipMutationResult, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
let ownership = Self::parse_ownership_context(&json_data)?;
|
||||
if ownership.requester_uid.trim().is_empty() {
|
||||
return Err("Missing task ID or requester UID.".to_string());
|
||||
}
|
||||
|
||||
if self.get_status(entry_id.clone())? != "active" {
|
||||
return Err("Task is no longer active.".to_string());
|
||||
}
|
||||
|
||||
if let Some(existing) = self.repository.get_ownership(&entry_id)?
|
||||
&& !existing.requester_uid.trim().is_empty()
|
||||
&& existing.requester_uid != ownership.requester_uid
|
||||
{
|
||||
return Err("Task has already been accepted.".to_string());
|
||||
}
|
||||
|
||||
let mut result = self.bind_ownership(
|
||||
entry_id,
|
||||
serde_json::to_string(&ownership)
|
||||
.map_err(|error| format!("Failed to serialize task ownership: {error}"))?,
|
||||
)?;
|
||||
result.message = "Task accepted.".to_string();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn set_status(&self, entry_id: String, status: String) -> Result<bool, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
let final_status = Self::validate_status(status)?;
|
||||
self.repository
|
||||
.set_active_status(entry_id.clone(), final_status.clone())?;
|
||||
if matches!(final_status.as_str(), "succeeded" | "failed") {
|
||||
self.repository
|
||||
.set_completed_status(entry_id, final_status)?;
|
||||
} else {
|
||||
self.repository.delete_completed_status(&entry_id)?;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn get_status(&self, entry_id: String) -> Result<String, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
if let Some(status) = self.repository.get_active_status(&entry_id)? {
|
||||
return Ok(status);
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.repository
|
||||
.get_completed_status(&entry_id)?
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn clear_status(&self, entry_id: String) -> Result<bool, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
self.repository.delete_active_status(&entry_id)?;
|
||||
self.repository.delete_completed_status(&entry_id)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn get_reward_context(&self, entry_id: String) -> Result<TaskRewardContext, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
let ownership = self
|
||||
.repository
|
||||
.get_ownership(&entry_id)?
|
||||
.unwrap_or_default();
|
||||
Ok(TaskRewardContext {
|
||||
requester_uid: ownership.requester_uid,
|
||||
org_id: ownership.org_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn increment_defuse_count(&self, entry_id: String) -> Result<u64, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
self.repository.increment_defuse_count(&entry_id)
|
||||
}
|
||||
|
||||
pub fn get_defuse_count(&self, entry_id: String) -> Result<u64, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
self.repository.get_defuse_count(&entry_id)
|
||||
}
|
||||
|
||||
pub fn clear_task(&self, entry_id: String) -> Result<bool, String> {
|
||||
let entry_id = Self::validate_entry_id(entry_id)?;
|
||||
self.repository.delete_catalog_entry(&entry_id)?;
|
||||
self.repository.delete_ownership(&entry_id)?;
|
||||
self.repository.delete_active_status(&entry_id)?;
|
||||
self.repository.delete_completed_status(&entry_id)?;
|
||||
self.repository.clear_defuse_count(&entry_id)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn patch_catalog_ownership(
|
||||
&self,
|
||||
entry_id: &str,
|
||||
accepted: bool,
|
||||
requester_uid: &str,
|
||||
org_id: &str,
|
||||
) -> Result<Value, String> {
|
||||
let Some(mut entry) = self.repository.get_catalog_entry(entry_id)? else {
|
||||
return Ok(Value::Null);
|
||||
};
|
||||
|
||||
entry
|
||||
.fields
|
||||
.insert("accepted".to_string(), Value::Bool(accepted));
|
||||
entry.fields.insert(
|
||||
"requesterUid".to_string(),
|
||||
Value::String(requester_uid.to_string()),
|
||||
);
|
||||
entry
|
||||
.fields
|
||||
.insert("orgID".to_string(), Value::String(org_id.to_string()));
|
||||
Self::normalize_catalog_entry(&mut entry, entry_id);
|
||||
self.repository
|
||||
.save_catalog_entry(entry_id.to_string(), entry.clone())?;
|
||||
Ok(entry.into_value())
|
||||
}
|
||||
|
||||
fn normalize_catalog_entry(entry: &mut TaskRecord, entry_id: &str) {
|
||||
let fields = &mut entry.fields;
|
||||
fields
|
||||
.entry("accepted".to_string())
|
||||
.or_insert(Value::Bool(false));
|
||||
fields
|
||||
.entry("requesterUid".to_string())
|
||||
.or_insert(Value::String(String::new()));
|
||||
fields
|
||||
.entry("orgID".to_string())
|
||||
.or_insert(Value::String("default".to_string()));
|
||||
fields
|
||||
.entry("taskId".to_string())
|
||||
.or_insert(Value::String(entry_id.to_string()));
|
||||
fields
|
||||
.entry("taskID".to_string())
|
||||
.or_insert(Value::String(entry_id.to_string()));
|
||||
}
|
||||
|
||||
fn validate_entry_id(entry_id: String) -> Result<String, String> {
|
||||
if entry_id.trim().is_empty() {
|
||||
return Err("Task ID is required.".to_string());
|
||||
}
|
||||
|
||||
Ok(entry_id)
|
||||
}
|
||||
|
||||
fn validate_status(status: String) -> Result<String, String> {
|
||||
if status.trim().is_empty() {
|
||||
return Err("Task status is required.".to_string());
|
||||
}
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
fn parse_record(json_data: &str) -> Result<TaskRecord, String> {
|
||||
serde_json::from_str::<TaskRecord>(json_data)
|
||||
.map_err(|error| format!("Invalid task JSON: {error}"))
|
||||
}
|
||||
|
||||
fn parse_ownership_context(json_data: &str) -> Result<TaskOwnershipContext, String> {
|
||||
serde_json::from_str::<TaskOwnershipContext>(json_data)
|
||||
.map_err(|error| format!("Invalid task ownership JSON: {error}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TaskStateService;
|
||||
use forge_repositories::{InMemoryTaskRepository, TaskRepository};
|
||||
use serde_json::Value;
|
||||
|
||||
#[test]
|
||||
fn bind_ownership_updates_catalog_entry() {
|
||||
let repository = InMemoryTaskRepository::new();
|
||||
let service = TaskStateService::new(repository.clone());
|
||||
|
||||
service
|
||||
.upsert_catalog_entry("task-1".to_string(), r#"{"title":"Attack"}"#.to_string())
|
||||
.expect("catalog upsert should succeed");
|
||||
|
||||
let result = service
|
||||
.bind_ownership(
|
||||
"task-1".to_string(),
|
||||
r#"{"requesterUid":"uid-1","orgId":"org-1"}"#.to_string(),
|
||||
)
|
||||
.expect("bind should succeed");
|
||||
|
||||
assert_eq!(result.requester_uid, "uid-1");
|
||||
assert_eq!(result.org_id, "org-1");
|
||||
assert_eq!(
|
||||
result.entry.get("accepted").and_then(Value::as_bool),
|
||||
Some(true)
|
||||
);
|
||||
|
||||
let stored = repository
|
||||
.get_catalog_entry("task-1")
|
||||
.expect("catalog lookup should succeed")
|
||||
.expect("catalog entry should exist");
|
||||
assert_eq!(
|
||||
stored.fields.get("requesterUid").and_then(Value::as_str),
|
||||
Some("uid-1")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_status_falls_back_to_completed_status() {
|
||||
let repository = InMemoryTaskRepository::new();
|
||||
let service = TaskStateService::new(repository.clone());
|
||||
|
||||
service
|
||||
.set_status("task-1".to_string(), "failed".to_string())
|
||||
.expect("status update should succeed");
|
||||
repository
|
||||
.delete_active_status("task-1")
|
||||
.expect("active status delete should succeed");
|
||||
|
||||
assert_eq!(
|
||||
service
|
||||
.get_status("task-1".to_string())
|
||||
.expect("status lookup should succeed"),
|
||||
"failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_active_catalog_only_returns_active_entries() {
|
||||
let service = TaskStateService::new(InMemoryTaskRepository::new());
|
||||
|
||||
service
|
||||
.upsert_catalog_entry(
|
||||
"task-active".to_string(),
|
||||
r#"{"title":"Active"}"#.to_string(),
|
||||
)
|
||||
.expect("active catalog upsert should succeed");
|
||||
service
|
||||
.upsert_catalog_entry("task-done".to_string(), r#"{"title":"Done"}"#.to_string())
|
||||
.expect("done catalog upsert should succeed");
|
||||
service
|
||||
.set_status("task-active".to_string(), "active".to_string())
|
||||
.expect("active status update should succeed");
|
||||
service
|
||||
.set_status("task-done".to_string(), "succeeded".to_string())
|
||||
.expect("done status update should succeed");
|
||||
|
||||
let active_catalog = service
|
||||
.list_active_catalog()
|
||||
.expect("active catalog should build");
|
||||
|
||||
assert_eq!(active_catalog.len(), 1);
|
||||
assert_eq!(
|
||||
active_catalog[0].get("taskId").and_then(Value::as_str),
|
||||
Some("task-active")
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user