Add org leave/disband bridge flow across client and server
- Introduce `OrgUIBridge` to centralize UI event/request/response handling - Add leave and disband org requests with server handlers and client bridge events - Enforce portal permissions for leaving/disbanding and protect owner/self from member removal
This commit is contained in:
parent
6eb6ac79d1
commit
9cd7278746
@ -1,3 +1,4 @@
|
|||||||
PREP(handleUIEvents);
|
PREP(handleUIEvents);
|
||||||
PREP(initOrgClass);
|
PREP(initOrgClass);
|
||||||
|
PREP(initOrgUIBridge);
|
||||||
PREP(openUI);
|
PREP(openUI);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "script_component.hpp"
|
#include "script_component.hpp"
|
||||||
|
|
||||||
if (isNil QGVAR(OrgClass)) then { call FUNC(initOrgClass); };
|
if (isNil QGVAR(OrgClass)) then { call FUNC(initOrgClass); };
|
||||||
|
if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initOrgUIBridge); };
|
||||||
|
|
||||||
[QGVAR(initOrg), {
|
[QGVAR(initOrg), {
|
||||||
GVAR(OrgClass) call ["init", []];
|
GVAR(OrgClass) call ["init", []];
|
||||||
@ -21,27 +22,19 @@ if (isNil QGVAR(OrgClass)) then { call FUNC(initOrgClass); };
|
|||||||
[QGVAR(responseCreateOrg), {
|
[QGVAR(responseCreateOrg), {
|
||||||
params [["_payload", createHashMap, [createHashMap]]];
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
private _control = uiNamespace getVariable [QGVAR(PendingBrowserControl), controlNull];
|
GVAR(OrgUIBridge) call ["handleCreateResponse", [_payload]];
|
||||||
uiNamespace setVariable [QGVAR(PendingBrowserControl), controlNull];
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
private _success = _payload getOrDefault ["success", false];
|
[QGVAR(responseDisbandOrg), {
|
||||||
if (!_success) exitWith {
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
if (_control isNotEqualTo controlNull) then {
|
|
||||||
private _json = toJSON (createHashMapFromArray [
|
|
||||||
["message", _payload getOrDefault ["message", "Organization registration failed."]]
|
|
||||||
]);
|
|
||||||
|
|
||||||
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.receiveCreateFailure(%1)", _json]];
|
GVAR(OrgUIBridge) call ["handleDisbandResponse", [_payload]];
|
||||||
};
|
}] call CFUNC(addEventHandler);
|
||||||
};
|
|
||||||
|
|
||||||
private _orgData = _payload getOrDefault ["org", createHashMap];
|
[QGVAR(responseLeaveOrg), {
|
||||||
GVAR(OrgClass) call ["sync", [_orgData, true]];
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
if (_control isNotEqualTo controlNull) then {
|
GVAR(OrgUIBridge) call ["handleLeaveResponse", [_payload]];
|
||||||
private _json = toJSON (GVAR(OrgClass) call ["buildPortalPayload", []]);
|
|
||||||
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.receiveCreateSuccess(%1)", _json]];
|
|
||||||
};
|
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
|
|||||||
@ -21,52 +21,25 @@ params ["_control", "_isConfirmDialog", "_message"];
|
|||||||
private _alert = fromJSON _message;
|
private _alert = fromJSON _message;
|
||||||
private _event = _alert get "event";
|
private _event = _alert get "event";
|
||||||
private _data = _alert get "data";
|
private _data = _alert get "data";
|
||||||
// private _display = displayChild findDisplay 46;
|
|
||||||
|
|
||||||
private _fnc_execBridge = {
|
|
||||||
params ["_control", "_fnName", "_payload"];
|
|
||||||
|
|
||||||
private _json = toJSON _payload;
|
|
||||||
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.%1(%2)", _fnName, _json]];
|
|
||||||
};
|
|
||||||
|
|
||||||
diag_log format ["[FORGE:Client:Org] Handling UI event: %1 with data: %2", _event, _data];
|
diag_log format ["[FORGE:Client:Org] Handling UI event: %1 with data: %2", _event, _data];
|
||||||
|
|
||||||
switch (_event) do {
|
switch (_event) do {
|
||||||
case "org::close": { closeDialog 1; };
|
case "org::close": { closeDialog 1; };
|
||||||
case "org::login::request": {
|
case "org::login::request": {
|
||||||
private _orgData = GVAR(OrgClass) get "org";
|
GVAR(OrgUIBridge) call ["handleLoginRequest", [_control]];
|
||||||
private _orgId = _orgData getOrDefault ["id", ""];
|
|
||||||
private _orgName = _orgData getOrDefault ["name", ""];
|
|
||||||
|
|
||||||
if (_orgId isEqualTo "" && {_orgName isEqualTo ""}) exitWith {
|
|
||||||
[_control, "receiveLoginFailure", createHashMapFromArray [
|
|
||||||
["message", "No organization data is available for this player."]
|
|
||||||
]] call _fnc_execBridge;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _payload = GVAR(OrgClass) call ["buildPortalPayload", []];
|
|
||||||
[_control, "receiveLoginSuccess", _payload] call _fnc_execBridge;
|
|
||||||
};
|
};
|
||||||
case "org::create::request": {
|
case "org::create::request": {
|
||||||
private _orgName = _data getOrDefault ["orgName", ""];
|
GVAR(OrgUIBridge) call ["handleCreateRequest", [_control, _data]];
|
||||||
|
};
|
||||||
if (_orgName isEqualTo "") exitWith {
|
case "org::disband::request": {
|
||||||
[_control, "receiveCreateFailure", createHashMapFromArray [
|
GVAR(OrgUIBridge) call ["requestDisband", []];
|
||||||
["message", "Enter an organization name."]
|
};
|
||||||
]] call _fnc_execBridge;
|
case "org::leave::request": {
|
||||||
};
|
GVAR(OrgUIBridge) call ["requestLeave", []];
|
||||||
|
|
||||||
uiNamespace setVariable [QGVAR(PendingBrowserControl), _control];
|
|
||||||
[SRPC(org,requestCreateOrg), [getPlayerUID player, _orgName]] call CFUNC(serverEvent);
|
|
||||||
};
|
};
|
||||||
case "org::ready": {
|
case "org::ready": {
|
||||||
[_control, "receive", createHashMapFromArray [
|
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
||||||
["event", "org::ready"],
|
|
||||||
["data", createHashMapFromArray [
|
|
||||||
["loaded", true]
|
|
||||||
]]
|
|
||||||
]] call _fnc_execBridge;
|
|
||||||
};
|
};
|
||||||
default { hint format ["Unhandled UI event: %1", _event]; };
|
default { hint format ["Unhandled UI event: %1", _event]; };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -101,7 +101,10 @@ GVAR(OrgBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
if (_memberUid isEqualTo _ownerUid && {_ownerName isEqualTo ""}) then { _ownerName = _memberName; };
|
if (_memberUid isEqualTo _ownerUid && {_ownerName isEqualTo ""}) then { _ownerName = _memberName; };
|
||||||
if (_memberUid isEqualTo _playerUid) then { _sessionRole = "Member"; };
|
if (_memberUid isEqualTo _playerUid) then { _sessionRole = "Member"; };
|
||||||
|
|
||||||
_membersList pushBack (createHashMapFromArray [["name", _memberName]]);
|
_membersList pushBack (createHashMapFromArray [
|
||||||
|
["uid", _memberUid],
|
||||||
|
["name", _memberName]
|
||||||
|
]);
|
||||||
} forEach _membersRaw;
|
} forEach _membersRaw;
|
||||||
|
|
||||||
if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _playerUid }) then { _ownerName = _playerName; };
|
if (_ownerName isEqualTo "" && { _ownerUid isEqualTo _playerUid }) then { _ownerName = _playerName; };
|
||||||
|
|||||||
173
arma/client/addons/org/functions/fnc_initOrgUIBridge.sqf
Normal file
173
arma/client/addons/org/functions/fnc_initOrgUIBridge.sqf
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File: fnc_initOrgUIBridge.sqf
|
||||||
|
* Author: IDSolutions
|
||||||
|
* Date: 2026-03-10
|
||||||
|
* Last Update: 2026-03-10
|
||||||
|
* Public: No
|
||||||
|
*
|
||||||
|
* Description:
|
||||||
|
* Initializes the org UI bridge for browser control state and event routing.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* None
|
||||||
|
*
|
||||||
|
* Return Value:
|
||||||
|
* Org UI bridge object [HASHMAP OBJECT]
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* call forge_client_org_fnc_initOrgUIBridge
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma hemtt ignore_variables ["_self"]
|
||||||
|
GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
||||||
|
["#type", "OrgUIBridgeBaseClass"],
|
||||||
|
["#create", compileFinal {
|
||||||
|
_self set ["pendingBrowserControl", controlNull];
|
||||||
|
}],
|
||||||
|
["setPendingBrowserControl", compileFinal {
|
||||||
|
params [["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
|
_self set ["pendingBrowserControl", _control];
|
||||||
|
_control
|
||||||
|
}],
|
||||||
|
["consumePendingBrowserControl", compileFinal {
|
||||||
|
private _control = _self getOrDefault ["pendingBrowserControl", controlNull];
|
||||||
|
_self set ["pendingBrowserControl", controlNull];
|
||||||
|
|
||||||
|
_control
|
||||||
|
}],
|
||||||
|
["getActiveBrowserControl", compileFinal {
|
||||||
|
private _display = uiNamespace getVariable ["RscOrg", displayNull];
|
||||||
|
if (isNull _display) exitWith { controlNull };
|
||||||
|
|
||||||
|
_display displayCtrl 1003
|
||||||
|
}],
|
||||||
|
["execBridge", compileFinal {
|
||||||
|
params [
|
||||||
|
["_control", controlNull, [controlNull]],
|
||||||
|
["_fnName", "", [""]],
|
||||||
|
["_payload", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isNull _control || { _fnName isEqualTo "" }) exitWith { false };
|
||||||
|
|
||||||
|
private _json = toJSON _payload;
|
||||||
|
_control ctrlWebBrowserAction ["ExecJS", format ["OrgUIBridge.%1(%2)", _fnName, _json]];
|
||||||
|
|
||||||
|
true
|
||||||
|
}],
|
||||||
|
["sendBridgeEvent", compileFinal {
|
||||||
|
params [
|
||||||
|
["_event", "", [""]],
|
||||||
|
["_data", createHashMap, [createHashMap]],
|
||||||
|
["_control", controlNull, [controlNull]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_event isEqualTo "") exitWith { false };
|
||||||
|
|
||||||
|
private _targetControl = _control;
|
||||||
|
if (isNull _targetControl) then {
|
||||||
|
_targetControl = _self call ["getActiveBrowserControl", []];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNull _targetControl) exitWith { false };
|
||||||
|
|
||||||
|
_self call ["execBridge", [_targetControl, "receive", createHashMapFromArray [
|
||||||
|
["event", _event],
|
||||||
|
["data", _data]
|
||||||
|
]]]
|
||||||
|
}],
|
||||||
|
["handleLoginRequest", compileFinal {
|
||||||
|
params [["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
|
private _orgData = GVAR(OrgClass) get "org";
|
||||||
|
private _orgId = _orgData getOrDefault ["id", ""];
|
||||||
|
private _orgName = _orgData getOrDefault ["name", ""];
|
||||||
|
|
||||||
|
if (_orgId isEqualTo "" && { _orgName isEqualTo "" }) exitWith {
|
||||||
|
_self call ["execBridge", [_control, "receiveLoginFailure", createHashMapFromArray [
|
||||||
|
["message", "No organization data is available for this player."]
|
||||||
|
]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["execBridge", [_control, "receiveLoginSuccess", GVAR(OrgClass) call ["buildPortalPayload", []]]];
|
||||||
|
}],
|
||||||
|
["handleCreateRequest", compileFinal {
|
||||||
|
params [
|
||||||
|
["_control", controlNull, [controlNull]],
|
||||||
|
["_data", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _orgName = _data getOrDefault ["orgName", ""];
|
||||||
|
if (_orgName isEqualTo "") exitWith {
|
||||||
|
_self call ["execBridge", [_control, "receiveCreateFailure", createHashMapFromArray [
|
||||||
|
["message", "Enter an organization name."]
|
||||||
|
]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["setPendingBrowserControl", [_control]];
|
||||||
|
[SRPC(org,requestCreateOrg), [getPlayerUID player, _orgName]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["handleCreateResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _control = _self call ["consumePendingBrowserControl", []];
|
||||||
|
private _success = _payload getOrDefault ["success", false];
|
||||||
|
if (!_success) exitWith {
|
||||||
|
if (isNull _control) exitWith {};
|
||||||
|
|
||||||
|
_self call ["execBridge", [_control, "receiveCreateFailure", createHashMapFromArray [
|
||||||
|
["message", _payload getOrDefault ["message", "Organization registration failed."]]
|
||||||
|
]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _orgData = _payload getOrDefault ["org", createHashMap];
|
||||||
|
GVAR(OrgClass) call ["sync", [_orgData, true]];
|
||||||
|
|
||||||
|
if (isNull _control) exitWith {};
|
||||||
|
_self call ["execBridge", [_control, "receiveCreateSuccess", GVAR(OrgClass) call ["buildPortalPayload", []]]];
|
||||||
|
}],
|
||||||
|
["handleDisbandResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = if (_payload getOrDefault ["success", false]) then {
|
||||||
|
["org::portal::revoked", "org::disband::success"] select (_payload getOrDefault ["requester", false])
|
||||||
|
} else {
|
||||||
|
"org::disband::failure"
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["sendBridgeEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
|
["handleLeaveResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = [
|
||||||
|
"org::leave::failure",
|
||||||
|
"org::leave::success"
|
||||||
|
] select (_payload getOrDefault ["success", false]);
|
||||||
|
|
||||||
|
_self call ["sendBridgeEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
|
["requestDisband", compileFinal {
|
||||||
|
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["requestLeave", compileFinal {
|
||||||
|
[SRPC(org,requestLeaveOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["handleReady", compileFinal {
|
||||||
|
params [["_control", controlNull, [controlNull]]];
|
||||||
|
|
||||||
|
_self call ["sendBridgeEvent", [
|
||||||
|
"org::ready",
|
||||||
|
createHashMapFromArray [
|
||||||
|
["loaded", true]
|
||||||
|
],
|
||||||
|
_control
|
||||||
|
]];
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) = createHashMapObject [GVAR(OrgUIBridgeBaseClass)];
|
||||||
|
GVAR(OrgUIBridge)
|
||||||
@ -41,6 +41,36 @@
|
|||||||
store.failCreate("Arma registration bridge is unavailable.");
|
store.failCreate("Arma registration bridge is unavailable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestDisbandOrg() {
|
||||||
|
const sent = sendEvent("org::disband::request", {});
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma disband bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestLeaveOrg() {
|
||||||
|
const sent = sendEvent("org::leave::request", {});
|
||||||
|
if (sent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma leave bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function receive(eventOrPayload, data = {}) {
|
function receive(eventOrPayload, data = {}) {
|
||||||
const event =
|
const event =
|
||||||
typeof eventOrPayload === "object" && eventOrPayload !== null
|
typeof eventOrPayload === "object" && eventOrPayload !== null
|
||||||
@ -70,12 +100,76 @@
|
|||||||
store.failCreate(
|
store.failCreate(
|
||||||
payloadData.message || "Organization registration failed.",
|
payloadData.message || "Organization registration failed.",
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (event === "org::disband::success") {
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
OrgPortal.store.setOrgDisbanded(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::disband::failure") {
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Organization disbanding failed.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::leave::success") {
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
store.failLogin(
|
||||||
|
payloadData.message || "You have left the organization.",
|
||||||
|
);
|
||||||
|
store.setView("home");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::leave::failure") {
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Unable to leave the organization.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event === "org::portal::revoked") {
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
store.failLogin(
|
||||||
|
payloadData.message ||
|
||||||
|
"Organization access is no longer available.",
|
||||||
|
);
|
||||||
|
store.setView("home");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistryApp.bridge = {
|
RegistryApp.bridge = {
|
||||||
requestLogin,
|
requestLogin,
|
||||||
requestCreateOrg,
|
requestCreateOrg,
|
||||||
|
requestDisbandOrg,
|
||||||
|
requestLeaveOrg,
|
||||||
receive,
|
receive,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
};
|
};
|
||||||
@ -83,6 +177,8 @@
|
|||||||
window.OrgUIBridge = {
|
window.OrgUIBridge = {
|
||||||
requestLogin,
|
requestLogin,
|
||||||
requestCreateOrg,
|
requestCreateOrg,
|
||||||
|
requestDisbandOrg,
|
||||||
|
requestLeaveOrg,
|
||||||
receive,
|
receive,
|
||||||
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
receiveLoginSuccess: (data) => receive("org::login::success", data),
|
||||||
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
receiveLoginFailure: (data) => receive("org::login::failure", data),
|
||||||
|
|||||||
@ -71,6 +71,14 @@
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
const view = store.getView();
|
const view = store.getView();
|
||||||
|
const portalPermissions =
|
||||||
|
window.OrgPortal && window.OrgPortal.permissions
|
||||||
|
? window.OrgPortal.permissions
|
||||||
|
: null;
|
||||||
|
const portalActions =
|
||||||
|
window.OrgPortal && window.OrgPortal.actions
|
||||||
|
? window.OrgPortal.actions
|
||||||
|
: null;
|
||||||
const viewLabel =
|
const viewLabel =
|
||||||
view === "create"
|
view === "create"
|
||||||
? "Organization Registration"
|
? "Organization Registration"
|
||||||
@ -116,6 +124,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (view === "portal" && PortalApp) {
|
if (view === "portal" && PortalApp) {
|
||||||
|
const canLeaveOrg =
|
||||||
|
portalPermissions &&
|
||||||
|
typeof portalPermissions.canLeaveOrg === "function" &&
|
||||||
|
portalPermissions.canLeaveOrg();
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "app-shell" },
|
{ className: "app-shell" },
|
||||||
@ -126,6 +139,13 @@
|
|||||||
Navbar({
|
Navbar({
|
||||||
title: "Global Organization Network",
|
title: "Global Organization Network",
|
||||||
viewLabel,
|
viewLabel,
|
||||||
|
actionLabel: canLeaveOrg ? "Leave Organization" : "",
|
||||||
|
onAction:
|
||||||
|
canLeaveOrg &&
|
||||||
|
portalActions &&
|
||||||
|
typeof portalActions.openModal === "function"
|
||||||
|
? () => portalActions.openModal("leave")
|
||||||
|
: null,
|
||||||
}),
|
}),
|
||||||
PortalApp(),
|
PortalApp(),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,10 +3,26 @@
|
|||||||
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
const { h, ensureScopedStyle } = OrgPortal.runtime;
|
||||||
const scopeAttr = "data-ui-future-card";
|
const scopeAttr = "data-ui-future-card";
|
||||||
const ROADMAP = [
|
const ROADMAP = [
|
||||||
{ name: "Contracts Board", status: "Planned", detail: "Track payouts, assignments, and claim approvals." },
|
{
|
||||||
{ name: "Diplomacy", status: "Future Review", detail: "Possible future module pending a full design and scope review." },
|
name: "Contracts Board",
|
||||||
{ name: "Logistics Queue", status: "Future Review", detail: "Possible future module pending a full design and scope review." },
|
status: "Planned",
|
||||||
{ name: "Permissions", status: "Future Review", detail: "Possible future module pending a full design and scope review." },
|
detail: "Track payouts, assignments, and claim approvals.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Diplomacy",
|
||||||
|
status: "Future Review",
|
||||||
|
detail: "Possible future module pending a full design and scope review.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Logistics Queue",
|
||||||
|
status: "Future Review",
|
||||||
|
detail: "Possible future module pending a full design and scope review.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Permissions",
|
||||||
|
status: "Future Review",
|
||||||
|
detail: "Possible future module pending a full design and scope review.",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const scopeSelector = `[${scopeAttr}]`;
|
const scopeSelector = `[${scopeAttr}]`;
|
||||||
const futureCardCss = `
|
const futureCardCss = `
|
||||||
|
|||||||
@ -63,7 +63,7 @@ ${scopeSelector} .org-name-row button {
|
|||||||
className: "org-scroll-panel org-span-5",
|
className: "org-scroll-panel org-span-5",
|
||||||
title: "Members",
|
title: "Members",
|
||||||
subtitle:
|
subtitle:
|
||||||
"Current roster listing. The organization owner cannot be removed.",
|
"Current roster listing. The organization owner and your own member entry cannot be removed.",
|
||||||
rootProps: { [scopeAttr]: "" },
|
rootProps: { [scopeAttr]: "" },
|
||||||
body: h(
|
body: h(
|
||||||
"div",
|
"div",
|
||||||
@ -71,7 +71,7 @@ ${scopeSelector} .org-name-row button {
|
|||||||
...members.map((member) => {
|
...members.map((member) => {
|
||||||
const canRemoveMember =
|
const canRemoveMember =
|
||||||
allowMemberManagement &&
|
allowMemberManagement &&
|
||||||
!actions.isOwnerMember(member.name);
|
!actions.isProtectedMember(member);
|
||||||
|
|
||||||
return h(
|
return h(
|
||||||
"article",
|
"article",
|
||||||
@ -86,7 +86,7 @@ ${scopeSelector} .org-name-row button {
|
|||||||
title: `Remove ${member.name}`,
|
title: `Remove ${member.name}`,
|
||||||
"aria-label": `Remove ${member.name}`,
|
"aria-label": `Remove ${member.name}`,
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
actions.removeMember(member.name),
|
actions.removeMember(member),
|
||||||
},
|
},
|
||||||
h(
|
h(
|
||||||
"svg",
|
"svg",
|
||||||
|
|||||||
@ -246,6 +246,41 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else if (modal.type === "leave") {
|
||||||
|
title = "Leave Organization";
|
||||||
|
body = h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-danger" },
|
||||||
|
h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"Leave ",
|
||||||
|
portalData.org.name,
|
||||||
|
" and return to the default organization?",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-danger-actions" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () => actions.closeModal(),
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-danger-btn",
|
||||||
|
onClick: () => actions.leaveOrganization(),
|
||||||
|
},
|
||||||
|
"Confirm Leave",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Modal({
|
return Modal({
|
||||||
|
|||||||
@ -88,17 +88,58 @@
|
|||||||
return el ? el.value : "";
|
return el ? el.value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
isOwnerMember(memberName) {
|
getMemberName(member) {
|
||||||
|
if (member && typeof member === "object") {
|
||||||
|
return String(member.name || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(member || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
getMemberUid(member) {
|
||||||
|
if (member && typeof member === "object") {
|
||||||
|
return String(member.uid || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
isOwnerMember(member) {
|
||||||
return (
|
return (
|
||||||
String(memberName || "")
|
this.getMemberName(member).trim().toLowerCase() ===
|
||||||
.trim()
|
|
||||||
.toLowerCase() ===
|
|
||||||
String(portalData.org.owner || "")
|
String(portalData.org.owner || "")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCurrentMember(member) {
|
||||||
|
const session = window.OrgPortal?.data?.session || {};
|
||||||
|
const memberUid = this.getMemberUid(member)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const actorUid = String(session.actorUid || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
if (memberUid && actorUid) {
|
||||||
|
return memberUid === actorUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.getMemberName(member).trim().toLowerCase() ===
|
||||||
|
String(session.actorName || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isProtectedMember(member) {
|
||||||
|
return (
|
||||||
|
this.isOwnerMember(member) || this.isCurrentMember(member)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
closePortal() {
|
closePortal() {
|
||||||
if (
|
if (
|
||||||
typeof A3API !== "undefined" &&
|
typeof A3API !== "undefined" &&
|
||||||
@ -136,6 +177,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "leave" && !permissions.canLeaveOrg()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
store.setModal({ type });
|
store.setModal({ type });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,18 +188,23 @@
|
|||||||
store.setModal(null);
|
store.setModal(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeMember(memberName) {
|
removeMember(member) {
|
||||||
if (!permissions.canManageMembers()) {
|
if (!permissions.canManageMembers()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isOwnerMember(memberName)) {
|
if (this.isProtectedMember(member)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const memberUid = this.getMemberUid(member);
|
||||||
|
const memberName = this.getMemberName(member);
|
||||||
|
|
||||||
store.setMembers((currentMembers) =>
|
store.setMembers((currentMembers) =>
|
||||||
currentMembers.filter(
|
currentMembers.filter((entry) =>
|
||||||
(member) => member.name !== memberName,
|
memberUid
|
||||||
|
? entry.uid !== memberUid
|
||||||
|
: entry.name !== memberName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
store.setCreditLines((currentLines) =>
|
store.setCreditLines((currentLines) =>
|
||||||
@ -168,8 +218,42 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setOrgDisbanded(true);
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestDisbandOrg !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Disband bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
|
bridge.requestDisbandOrg();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
leaveOrganization() {
|
||||||
|
if (!permissions.canLeaveOrg()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestLeaveOrg !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Leave bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.closeModal();
|
||||||
|
bridge.requestLeaveOrg();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -66,7 +66,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
canDisbandOrg() {
|
canDisbandOrg() {
|
||||||
return this.isOrgLeaderOrCeo();
|
return this.isOrgOwner() && !this.isDefaultOrg();
|
||||||
|
}
|
||||||
|
|
||||||
|
canLeaveOrg() {
|
||||||
|
return !this.isDefaultOrg() && !this.isOrgOwner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,11 +25,8 @@ PREP_RECOMPILE_END;
|
|||||||
private _result = GVAR(OrgStore) call ["register", [_uid, _orgName]];
|
private _result = GVAR(OrgStore) call ["register", [_uid, _orgName]];
|
||||||
|
|
||||||
if (_result getOrDefault ["success", false]) then {
|
if (_result getOrDefault ["success", false]) then {
|
||||||
private _org = _result getOrDefault ["org", createHashMap];
|
private _actorPatch = _result getOrDefault ["actorPatch", createHashMap];
|
||||||
private _orgID = _org getOrDefault ["id", ""];
|
if (_actorPatch isNotEqualTo createHashMap) then {
|
||||||
|
|
||||||
if (_orgID isNotEqualTo "") then {
|
|
||||||
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", _orgID, true]];
|
|
||||||
[CRPC(actor,responseSyncActor), [_actorPatch], _player] call CFUNC(targetEvent);
|
[CRPC(actor,responseSyncActor), [_actorPatch], _player] call CFUNC(targetEvent);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -89,5 +86,85 @@ PREP_RECOMPILE_END;
|
|||||||
|
|
||||||
private _index = GVAR(IndexRegistry) get _uid;
|
private _index = GVAR(IndexRegistry) get _uid;
|
||||||
private _key = _index get "orgID";
|
private _key = _index get "orgID";
|
||||||
GVAR(OrgStore) call ["remove", [GVAR(Registry), "org:update", _key]];
|
GVAR(OrgStore) call ["delete", [_key]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestLeaveOrg), {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Empty/Invalid UID for leave request!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_player isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["leave", [_uid]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
private _actorPatch = _result getOrDefault ["actorPatch", createHashMap];
|
||||||
|
if (_actorPatch isNotEqualTo createHashMap) then {
|
||||||
|
[CRPC(actor,responseSyncActor), [_actorPatch], _player] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(OrgStore) call ["init", [_uid]];
|
||||||
|
|
||||||
|
private _notificationParams = _result getOrDefault ["notification", []];
|
||||||
|
if (_notificationParams isEqualType [] && { count _notificationParams > 0 }) then {
|
||||||
|
[CRPC(notifications,recieveNotification), _notificationParams, _player] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseLeaveOrg), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to leave the organization."]]
|
||||||
|
]], _player] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestDisbandOrg), {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Empty/Invalid UID for disband request!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requester = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_requester isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["disband", [_uid]];
|
||||||
|
if !(_result getOrDefault ["success", false]) exitWith {
|
||||||
|
[CRPC(org,responseDisbandOrg), [createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", _result getOrDefault ["message", "Failed to disband organization."]],
|
||||||
|
["requester", true]
|
||||||
|
]], _requester] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
[_x, _result] call {
|
||||||
|
params [["_member", createHashMap, [createHashMap]], ["_disbandResult", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _memberUid = _member getOrDefault ["uid", ""];
|
||||||
|
if (_memberUid isEqualTo "") exitWith {};
|
||||||
|
|
||||||
|
private _memberPlayer = [_memberUid] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _actorPatch = _member getOrDefault ["actorPatch", createHashMap];
|
||||||
|
if (_actorPatch isNotEqualTo createHashMap) then {
|
||||||
|
[CRPC(actor,responseSyncActor), [_actorPatch], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(OrgStore) call ["init", [_memberUid]];
|
||||||
|
[CRPC(org,responseDisbandOrg), [createHashMapFromArray [
|
||||||
|
["success", true],
|
||||||
|
["message", _member getOrDefault ["message", _disbandResult getOrDefault ["message", "Organization disbanded."]]],
|
||||||
|
["requester", _member getOrDefault ["requester", false]]
|
||||||
|
]], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
|
||||||
|
private _notificationParams = _member getOrDefault ["notification", []];
|
||||||
|
if (_notificationParams isEqualType [] && { count _notificationParams > 0 }) then {
|
||||||
|
[CRPC(notifications,recieveNotification), _notificationParams, _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} forEach (_result getOrDefault ["members", []]);
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|||||||
@ -140,13 +140,281 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_org
|
_org
|
||||||
}],
|
}],
|
||||||
|
["loadById", compileFinal {
|
||||||
|
params [["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "") exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _cached = GVAR(Registry) getOrDefault [_orgID, createHashMap];
|
||||||
|
if (_cached isNotEqualTo createHashMap) exitWith { _cached };
|
||||||
|
|
||||||
|
["org:exists", [_orgID]] call EFUNC(extension,extCall) params ["_existsResult", "_existsSuccess"];
|
||||||
|
if (!_existsSuccess || { _existsResult isNotEqualTo "true" }) exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _org = _self call ["fetch", ["org:get", _orgID]];
|
||||||
|
if (_org isEqualTo createHashMap) exitWith { _org };
|
||||||
|
|
||||||
|
private _memberRows = _self call ["fetch", ["org:members:get", _orgID]];
|
||||||
|
if !(_memberRows isEqualType []) then {
|
||||||
|
_memberRows = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _memberMap = createHashMap;
|
||||||
|
{
|
||||||
|
private _memberUid = _x getOrDefault ["uid", ""];
|
||||||
|
if (_memberUid isNotEqualTo "") then {
|
||||||
|
_memberMap set [_memberUid, _x];
|
||||||
|
};
|
||||||
|
} forEach _memberRows;
|
||||||
|
|
||||||
|
_org set ["members", _memberMap];
|
||||||
|
GVAR(Registry) set [_orgID, _org, true];
|
||||||
|
_org
|
||||||
|
}],
|
||||||
|
["addMember", compileFinal {
|
||||||
|
params [
|
||||||
|
["_orgID", "", [""]],
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_player", objNull, [objNull]],
|
||||||
|
["_actor", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _org = _self call ["loadById", [_orgID]];
|
||||||
|
if (_org isEqualTo createHashMap) exitWith { _org };
|
||||||
|
|
||||||
|
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||||
|
GVAR(Registry) set [_orgID, _org, true];
|
||||||
|
_org
|
||||||
|
}],
|
||||||
|
["removeMember", compileFinal {
|
||||||
|
params [
|
||||||
|
["_orgID", "", [""]],
|
||||||
|
["_uid", "", [""]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "" || { _uid isEqualTo "" }) exitWith { createHashMap };
|
||||||
|
|
||||||
|
private _org = _self call ["loadById", [_orgID]];
|
||||||
|
if (_org isEqualTo createHashMap) exitWith { _org };
|
||||||
|
|
||||||
|
["org:members:remove", [_orgID, _uid]] call EFUNC(extension,extCall) params ["_memberResult", "_memberSuccess"];
|
||||||
|
if (!_memberSuccess) exitWith {
|
||||||
|
["WARNING", format ["Failed to remove %1 from org %2 members: %3", _uid, _orgID, _memberResult]] call EFUNC(common,log);
|
||||||
|
createHashMap
|
||||||
|
};
|
||||||
|
|
||||||
|
private _members = +(_org getOrDefault ["members", createHashMap]);
|
||||||
|
_members deleteAt _uid;
|
||||||
|
_org set ["members", _members];
|
||||||
|
GVAR(Registry) set [_orgID, _org, true];
|
||||||
|
|
||||||
|
_org
|
||||||
|
}],
|
||||||
|
["delete", compileFinal {
|
||||||
|
params [["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_orgID isEqualTo "" || { toLower _orgID isEqualTo "default" }) exitWith {
|
||||||
|
_result set ["message", "Invalid organization ID."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
["org:delete", [_orgID]] call EFUNC(extension,extCall) params ["_deleteResult", "_deleteSuccess"];
|
||||||
|
if (!_deleteSuccess || { _deleteResult isNotEqualTo "OK" }) exitWith {
|
||||||
|
_result set ["message", format ["Failed to delete organization: %1", _deleteResult]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(Registry) deleteAt _orgID;
|
||||||
|
_result set ["success", true];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["restoreDefaultMembership", compileFinal {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_player", objNull, [objNull]],
|
||||||
|
["_actor", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["actorPatch", createHashMap]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "A valid player UID is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _resolvedPlayer = _player;
|
||||||
|
if (_resolvedPlayer isEqualTo objNull) then {
|
||||||
|
_resolvedPlayer = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _resolvedActor = EGVAR(actor,Registry) getOrDefault [_uid, _actor];
|
||||||
|
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", "default", true]];
|
||||||
|
private _defaultOrg = _self call ["addMember", ["default", _uid, _resolvedPlayer, EGVAR(actor,Registry) getOrDefault [_uid, _resolvedActor]]];
|
||||||
|
if (_defaultOrg isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Failed to restore default organization membership."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(IndexRegistry) set [_uid, createHashMapFromArray [["orgID", "default"]]];
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["actorPatch", _actorPatch];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["leave", compileFinal {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["actorPatch", createHashMap],
|
||||||
|
["notification", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "A valid player UID is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||||
|
private _orgID = _actor getOrDefault ["organization", ""];
|
||||||
|
if (_orgID isEqualTo "" || { toLower _orgID isEqualTo "default" }) exitWith {
|
||||||
|
_result set ["message", "You are already assigned to the default organization."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _org = _self call ["loadById", [_orgID]];
|
||||||
|
if (_org isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Unable to load organization data for leave request."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||||
|
if (_ownerUid isEqualTo _uid) exitWith {
|
||||||
|
_result set ["message", "Organization owners must disband the organization instead of leaving it."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _orgName = _org getOrDefault ["name", "Organization"];
|
||||||
|
private _updatedOrg = _self call ["removeMember", [_orgID, _uid]];
|
||||||
|
if (_updatedOrg isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Failed to remove you from the organization roster."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _defaultResult = _self call ["restoreDefaultMembership", [_uid, _player, _actor]];
|
||||||
|
if !(_defaultResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", _defaultResult getOrDefault ["message", "Failed to restore default organization membership."]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _message = format ["You left %1 and returned to the default organization.", _orgName];
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _message];
|
||||||
|
_result set ["actorPatch", _defaultResult getOrDefault ["actorPatch", createHashMap]];
|
||||||
|
_result set ["notification", ["info", "Organization Left", _message, 6000]];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["disband", compileFinal {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["members", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith {
|
||||||
|
_result set ["message", "A valid player UID is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||||
|
private _orgID = _actor getOrDefault ["organization", ""];
|
||||||
|
if (_orgID isEqualTo "" || { toLower _orgID isEqualTo "default" }) exitWith {
|
||||||
|
_result set ["message", "Only active player organizations can be disbanded."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _org = _self call ["loadById", [_orgID]];
|
||||||
|
if (_org isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Unable to load organization data for disbanding."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _ownerUid = _org getOrDefault ["owner", ""];
|
||||||
|
if (_ownerUid isEqualTo "" || { _ownerUid isNotEqualTo _uid }) exitWith {
|
||||||
|
_result set ["message", "Only the organization owner can disband this organization."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _orgName = _org getOrDefault ["name", "Organization"];
|
||||||
|
private _memberMap = _org getOrDefault ["members", createHashMap];
|
||||||
|
private _memberUids = keys _memberMap;
|
||||||
|
if !(_uid in _memberUids) then {
|
||||||
|
_memberUids pushBack _uid;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _deleteResult = _self call ["delete", [_orgID]];
|
||||||
|
if !(_deleteResult getOrDefault ["success", false]) exitWith {
|
||||||
|
_result set ["message", _deleteResult getOrDefault ["message", "Failed to disband organization."]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _memberResults = [];
|
||||||
|
{
|
||||||
|
private _memberUid = _x;
|
||||||
|
if (_memberUid isNotEqualTo "") then {
|
||||||
|
private _memberPlayer = [_memberUid] call EFUNC(common,getPlayer);
|
||||||
|
private _defaultResult = _self call ["restoreDefaultMembership", [_memberUid, _memberPlayer, EGVAR(actor,Registry) getOrDefault [_memberUid, createHashMap]]];
|
||||||
|
if !(_defaultResult getOrDefault ["success", false]) then {
|
||||||
|
["WARNING", format ["Failed to restore default org for %1 after disbanding %2: %3", _memberUid, _orgID, _defaultResult getOrDefault ["message", "Unknown error."]]] call EFUNC(common,log);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _responseMessage = [
|
||||||
|
format ["%1 has been disbanded.", _orgName],
|
||||||
|
format ["Your organization, %1, has been disbanded.", _orgName]
|
||||||
|
] select (_memberUid isEqualTo _uid);
|
||||||
|
|
||||||
|
private _notificationParams = [
|
||||||
|
["warning", "Organization Disbanded", _responseMessage, 6000],
|
||||||
|
["success", "Organization Disbanded", _responseMessage, 6000]
|
||||||
|
] select (_memberUid isEqualTo _uid);
|
||||||
|
|
||||||
|
_memberResults pushBack (createHashMapFromArray [
|
||||||
|
["uid", _memberUid],
|
||||||
|
["requester", _memberUid isEqualTo _uid],
|
||||||
|
["message", _responseMessage],
|
||||||
|
["notification", _notificationParams],
|
||||||
|
["actorPatch", _defaultResult getOrDefault ["actorPatch", createHashMap]]
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
} forEach _memberUids;
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", format ["%1 has been disbanded.", _orgName]];
|
||||||
|
_result set ["members", _memberResults];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["register", compileFinal {
|
["register", compileFinal {
|
||||||
params [["_uid", "", [""]], ["_orgName", "", [""]]];
|
params [["_uid", "", [""]], ["_orgName", "", [""]]];
|
||||||
|
|
||||||
private _result = createHashMapFromArray [
|
private _result = createHashMapFromArray [
|
||||||
["success", false],
|
["success", false],
|
||||||
["message", ""],
|
["message", ""],
|
||||||
["org", createHashMap]
|
["org", createHashMap],
|
||||||
|
["actorPatch", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_uid isEqualTo "" || { _orgName isEqualTo "" }) exitWith {
|
if (_uid isEqualTo "" || { _orgName isEqualTo "" }) exitWith {
|
||||||
@ -158,7 +426,7 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
private _actor = EGVAR(actor,Registry) getOrDefault [_uid, createHashMap];
|
||||||
private _existingOrgID = _actor getOrDefault ["organization", ""];
|
private _existingOrgID = _actor getOrDefault ["organization", ""];
|
||||||
|
|
||||||
if (_existingOrgID isNotEqualTo "") exitWith {
|
if (_existingOrgID isNotEqualTo "" && { toLower _existingOrgID isNotEqualTo "default" }) exitWith {
|
||||||
_result set ["message", "Player already belongs to an organization."];
|
_result set ["message", "Player already belongs to an organization."];
|
||||||
_result
|
_result
|
||||||
};
|
};
|
||||||
@ -205,21 +473,20 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
_org set ["members", createHashMap];
|
_org set ["members", createHashMap];
|
||||||
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
_org = _self call ["verifyMember", [_org, _orgID, _uid, _player, _actor]];
|
||||||
|
|
||||||
["org:members:remove", ["default", _uid]] call EFUNC(extension,extCall);
|
if (toLower _existingOrgID isEqualTo "default") then {
|
||||||
|
private _defaultOrg = _self call ["removeMember", ["default", _uid]];
|
||||||
private _defaultOrg = GVAR(Registry) getOrDefault ["default", createHashMap];
|
if (_defaultOrg isEqualTo createHashMap) then {
|
||||||
if (_defaultOrg isNotEqualTo createHashMap) then {
|
["WARNING", format ["Failed to remove %1 from default org members after creating org %2.", _uid, _orgID]] call EFUNC(common,log);
|
||||||
private _defaultMembers = _defaultOrg getOrDefault ["members", createHashMap];
|
};
|
||||||
_defaultMembers deleteAt _uid;
|
|
||||||
_defaultOrg set ["members", _defaultMembers];
|
|
||||||
GVAR(Registry) set ["default", _defaultOrg, true];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private _actorPatch = EGVAR(actor,ActorStore) call ["set", [EGVAR(actor,Registry), "actor:update", _uid, "organization", _orgID, true]];
|
||||||
GVAR(IndexRegistry) set [_uid, createHashMapFromArray [["orgID", _orgID]]];
|
GVAR(IndexRegistry) set [_uid, createHashMapFromArray [["orgID", _orgID]]];
|
||||||
GVAR(Registry) set [_orgID, _org, true];
|
GVAR(Registry) set [_orgID, _org, true];
|
||||||
|
|
||||||
_result set ["success", true];
|
_result set ["success", true];
|
||||||
_result set ["org", _org];
|
_result set ["org", _org];
|
||||||
|
_result set ["actorPatch", _actorPatch];
|
||||||
_result
|
_result
|
||||||
}],
|
}],
|
||||||
["init", compileFinal {
|
["init", compileFinal {
|
||||||
@ -258,12 +525,9 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
|
|
||||||
private _finalOrg = createHashMap;
|
private _finalOrg = createHashMap;
|
||||||
private _finalAssets = createHashMap;
|
|
||||||
private _finalFleet = createHashMap;
|
|
||||||
private _finalMembers = createHashMap;
|
|
||||||
|
|
||||||
if (_result == "true") then {
|
if (_result == "true") then {
|
||||||
_finalOrg = _self call ["fetch", ["org:get", _orgID]];
|
_finalOrg = _self call ["loadById", [_orgID]];
|
||||||
["INFO", format ["Found org for %1", _orgID]] call EFUNC(common,log);
|
["INFO", format ["Found org for %1", _orgID]] call EFUNC(common,log);
|
||||||
} else {
|
} else {
|
||||||
["WARNING", format ["No existing org found for %1, using default org.", _uid]] call EFUNC(common,log);
|
["WARNING", format ["No existing org found for %1, using default org.", _uid]] call EFUNC(common,log);
|
||||||
@ -274,26 +538,6 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
private _entry = createHashMapFromArray [["orgID", _orgID]];
|
private _entry = createHashMapFromArray [["orgID", _orgID]];
|
||||||
GVAR(IndexRegistry) set [_uid, _entry];
|
GVAR(IndexRegistry) set [_uid, _entry];
|
||||||
|
|
||||||
// private _assets = _self call ["fetch", ["org:assets:get", _orgID]];
|
|
||||||
// private _fleet = _self call ["fetch", ["org:fleet:get", _orgID]];
|
|
||||||
private _members = _self call ["fetch", ["org:members:get", _orgID]];
|
|
||||||
|
|
||||||
{
|
|
||||||
private _key = _x get "uid";
|
|
||||||
private _value = _x;
|
|
||||||
_finalMembers set [_key, _value];
|
|
||||||
} forEach _members;
|
|
||||||
|
|
||||||
// {
|
|
||||||
// private _key = _x get "classname";
|
|
||||||
// private _value = _x;
|
|
||||||
// _finalAssets set [_key, _value];
|
|
||||||
// } forEach _assets;
|
|
||||||
|
|
||||||
_finalOrg set ["assets", _finalAssets];
|
|
||||||
_finalOrg set ["fleet", _finalFleet];
|
|
||||||
_finalOrg set ["members", _finalMembers];
|
|
||||||
|
|
||||||
private _finalOwner = _finalOrg getOrDefault ["owner", ""];
|
private _finalOwner = _finalOrg getOrDefault ["owner", ""];
|
||||||
if (_orgID isEqualTo "default" || { _finalOwner isEqualTo _uid }) then {
|
if (_orgID isEqualTo "default" || { _finalOwner isEqualTo _uid }) then {
|
||||||
_finalOrg = _self call ["verifyMember", [_finalOrg, _orgID, _uid, _player, _actor]];
|
_finalOrg = _self call ["verifyMember", [_finalOrg, _orgID, _uid, _player, _actor]];
|
||||||
|
|||||||
@ -147,15 +147,21 @@ impl<C: RedisClient> OrgRepository for RedisOrgRepository<C> {
|
|||||||
|
|
||||||
/// Permanently deletes an organization and all associated data from Redis.
|
/// Permanently deletes an organization and all associated data from Redis.
|
||||||
///
|
///
|
||||||
/// Removes the organization hash and the associated members list.
|
/// Removes the organization hash and related subordinate keys.
|
||||||
/// This operation is irreversible.
|
/// This operation is irreversible.
|
||||||
fn delete(&self, id: &str) -> Result<(), String> {
|
fn delete(&self, id: &str) -> Result<(), String> {
|
||||||
// Generate Redis key using organization ID
|
let redis_keys = [
|
||||||
let redis_key = format!("org:{}", id);
|
format!("org:{}", id),
|
||||||
|
format!("org:{}:members", id),
|
||||||
|
format!("org:{}:assets", id),
|
||||||
|
format!("org:{}:fleet", id),
|
||||||
|
];
|
||||||
|
|
||||||
// Delete the organization hash key from Redis
|
for redis_key in redis_keys {
|
||||||
// Note: This does NOT delete member data stored separately
|
self.client.delete_key(redis_key)?;
|
||||||
self.client.delete_key(redis_key)
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if an organization exists in Redis without retrieving the data.
|
/// Checks if an organization exists in Redis without retrieving the data.
|
||||||
|
|||||||
@ -163,16 +163,7 @@ impl<R: OrgRepository> OrgService<R> {
|
|||||||
///
|
///
|
||||||
/// Irreversible operation. Delegates to repository.
|
/// Irreversible operation. Delegates to repository.
|
||||||
pub fn delete_org(&self, key: String) -> Result<(), String> {
|
pub fn delete_org(&self, key: String) -> Result<(), String> {
|
||||||
let redis_key = format!("org:{}", key);
|
self.repository.delete(&key)
|
||||||
let assets_key = format!("org:{}:assets", key);
|
|
||||||
let members_key = format!("org:{}:members", key);
|
|
||||||
|
|
||||||
// Delegate deletion to repository layer
|
|
||||||
self.repository.delete(&redis_key)?;
|
|
||||||
self.repository.delete(&assets_key)?;
|
|
||||||
self.repository.delete(&members_key)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if an organization exists in the system.
|
/// Checks if an organization exists in the system.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user