Add org invite request and response handling
- Wire invite request, accept, and decline events through the UI bridge - Add client and server handlers for invite success and failure responses - Extend portal state and UI to support member invite actions
This commit is contained in:
parent
3599f802c8
commit
8a31d456f1
@ -50,6 +50,18 @@ if (isNil QGVAR(OrgUIBridge)) then { call FUNC(initUIBridge); };
|
|||||||
GVAR(OrgUIBridge) call ["handleCreditLineResponse", [_payload]];
|
GVAR(OrgUIBridge) call ["handleCreditLineResponse", [_payload]];
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseInviteOrg), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) call ["handleInviteResponse", [_payload]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(responseInviteDecision), {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
GVAR(OrgUIBridge) call ["handleInviteDecisionResponse", [_payload]];
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
EGVAR(actor,ActorRepository) get "isLoaded";
|
EGVAR(actor,ActorRepository) get "isLoaded";
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
@ -46,6 +46,15 @@ switch (_event) do {
|
|||||||
case "org::credit::request": {
|
case "org::credit::request": {
|
||||||
GVAR(OrgUIBridge) call ["requestCreditLine", [_data]];
|
GVAR(OrgUIBridge) call ["requestCreditLine", [_data]];
|
||||||
};
|
};
|
||||||
|
case "org::invite::request": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestInvite", [_data]];
|
||||||
|
};
|
||||||
|
case "org::invite::accept": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestAcceptInvite", [_data]];
|
||||||
|
};
|
||||||
|
case "org::invite::decline": {
|
||||||
|
GVAR(OrgUIBridge) call ["requestDeclineInvite", [_data]];
|
||||||
|
};
|
||||||
case "org::ready": {
|
case "org::ready": {
|
||||||
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
GVAR(OrgUIBridge) call ["handleReady", [_control]];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -161,6 +161,26 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}],
|
}],
|
||||||
|
["handleInviteResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = [
|
||||||
|
"org::invite::failure",
|
||||||
|
"org::invite::success"
|
||||||
|
] select (_payload getOrDefault ["success", false]);
|
||||||
|
|
||||||
|
_self call ["sendEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
|
["handleInviteDecisionResponse", compileFinal {
|
||||||
|
params [["_payload", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _eventName = [
|
||||||
|
"org::invite::decision::failure",
|
||||||
|
"org::invite::decision::success"
|
||||||
|
] select (_payload getOrDefault ["success", false]);
|
||||||
|
|
||||||
|
_self call ["sendEvent", [_eventName, _payload]];
|
||||||
|
}],
|
||||||
["requestDisband", compileFinal {
|
["requestDisband", compileFinal {
|
||||||
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
[SRPC(org,requestDisbandOrg), [getPlayerUID player]] call CFUNC(serverEvent);
|
||||||
}],
|
}],
|
||||||
@ -176,6 +196,25 @@ GVAR(OrgUIBridgeBaseClass) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
[SRPC(org,requestAssignCreditLine), [getPlayerUID player, _memberUid, _memberName, _amount]] call CFUNC(serverEvent);
|
[SRPC(org,requestAssignCreditLine), [getPlayerUID player, _memberUid, _memberName, _amount]] call CFUNC(serverEvent);
|
||||||
}],
|
}],
|
||||||
|
["requestInvite", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _targetUid = _data getOrDefault ["targetUid", ""];
|
||||||
|
private _targetName = _data getOrDefault ["targetName", ""];
|
||||||
|
[SRPC(org,requestInviteOrgMember), [getPlayerUID player, _targetUid, _targetName]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["requestAcceptInvite", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _orgID = _data getOrDefault ["orgId", ""];
|
||||||
|
[SRPC(org,requestAcceptOrgInvite), [getPlayerUID player, _orgID]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
|
["requestDeclineInvite", compileFinal {
|
||||||
|
params [["_data", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
|
private _orgID = _data getOrDefault ["orgId", ""];
|
||||||
|
[SRPC(org,requestDeclineOrgInvite), [getPlayerUID player, _orgID]] call CFUNC(serverEvent);
|
||||||
|
}],
|
||||||
["refreshPortal", compileFinal {
|
["refreshPortal", compileFinal {
|
||||||
_self call ["requestHydrate", ["org::sync"]]
|
_self call ["requestHydrate", ["org::sync"]]
|
||||||
}]
|
}]
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -80,6 +80,57 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestInvitePlayer(payload) {
|
||||||
|
const sent = sendEvent("org::invite::request", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestAcceptInvite(payload) {
|
||||||
|
const sent = sendEvent("org::invite::accept", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestDeclineInvite(payload) {
|
||||||
|
const sent = sendEvent("org::invite::decline", payload);
|
||||||
|
if (sent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Arma organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bridge.on("org::login::success", (payloadData) => {
|
bridge.on("org::login::success", (payloadData) => {
|
||||||
store.completeLogin(payloadData);
|
store.completeLogin(payloadData);
|
||||||
});
|
});
|
||||||
@ -128,6 +179,50 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::success", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.store) {
|
||||||
|
OrgPortal.store.setModal(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
payloadData.message || "Organization invite sent.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::failure", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Unable to send organization invite.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::decision::success", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"success",
|
||||||
|
payloadData.message || "Organization invite updated.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bridge.on("org::invite::decision::failure", (payloadData) => {
|
||||||
|
const OrgPortal = window.OrgPortal;
|
||||||
|
if (OrgPortal && OrgPortal.actions) {
|
||||||
|
OrgPortal.actions.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
payloadData.message || "Unable to update organization invite.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
bridge.on("org::member::creditUpdated", (payloadData) => {
|
bridge.on("org::member::creditUpdated", (payloadData) => {
|
||||||
const OrgPortal = window.OrgPortal;
|
const OrgPortal = window.OrgPortal;
|
||||||
if (!OrgPortal || !OrgPortal.store) {
|
if (!OrgPortal || !OrgPortal.store) {
|
||||||
@ -234,6 +329,9 @@
|
|||||||
requestDisbandOrg,
|
requestDisbandOrg,
|
||||||
requestLeaveOrg,
|
requestLeaveOrg,
|
||||||
requestCreditLine,
|
requestCreditLine,
|
||||||
|
requestInvitePlayer,
|
||||||
|
requestAcceptInvite,
|
||||||
|
requestDeclineInvite,
|
||||||
sendEvent,
|
sendEvent,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -19,6 +19,29 @@ ${scopeSelector} .org-name-list {
|
|||||||
scrollbar-color: #94a3b8 #e2e8f0;
|
scrollbar-color: #94a3b8 #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.85rem;
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-section h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
${scopeSelector} .org-name-row {
|
${scopeSelector} .org-name-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -39,13 +62,43 @@ ${scopeSelector} .org-name-row button {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-copy {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-name-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-inline-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
${scopeSelector} .org-members-empty {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
|
${scopeSelector} .org-members-head {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
${scopeSelector} .org-name-row {
|
${scopeSelector} .org-name-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
${scopeSelector} .org-name-row button {
|
${scopeSelector} .org-name-row button,
|
||||||
|
${scopeSelector} .org-inline-actions {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +109,7 @@ ${scopeSelector} .org-name-row button {
|
|||||||
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
OrgPortal.componentFns.MembersCard = function MembersCard() {
|
||||||
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
const PanelCard = window.SharedUI.componentFns.PanelCard;
|
||||||
const members = store.getMembers();
|
const members = store.getMembers();
|
||||||
|
const pendingInvites = store.getPendingInvites();
|
||||||
const allowMemberManagement = getters.canManageMembers();
|
const allowMemberManagement = getters.canManageMembers();
|
||||||
ensureScopedStyle("portal-members-card", membersCardCss);
|
ensureScopedStyle("portal-members-card", membersCardCss);
|
||||||
|
|
||||||
@ -68,6 +122,86 @@ ${scopeSelector} .org-name-row button {
|
|||||||
body: h(
|
body: h(
|
||||||
"div",
|
"div",
|
||||||
{ className: "org-name-list" },
|
{ className: "org-name-list" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-members-head" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-members-section" },
|
||||||
|
h("h4", null, "Pending Invites"),
|
||||||
|
pendingInvites.length === 0
|
||||||
|
? h(
|
||||||
|
"p",
|
||||||
|
{ className: "org-members-empty" },
|
||||||
|
"No incoming organization invites.",
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
allowMemberManagement
|
||||||
|
? h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () => actions.openModal("invite"),
|
||||||
|
},
|
||||||
|
"Invite Player",
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
...pendingInvites.map((invite) =>
|
||||||
|
h(
|
||||||
|
"article",
|
||||||
|
{ className: "org-name-row" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-name-copy" },
|
||||||
|
h(
|
||||||
|
"strong",
|
||||||
|
null,
|
||||||
|
invite.orgName || "Unknown Organization",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-name-meta" },
|
||||||
|
"Invited by ",
|
||||||
|
invite.inviterName || "Unknown",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-inline-actions" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () =>
|
||||||
|
actions.declineInvite(
|
||||||
|
String(invite.orgId || ""),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"Decline",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
onClick: () =>
|
||||||
|
actions.acceptInvite(
|
||||||
|
String(invite.orgId || ""),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"Accept",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-members-section" },
|
||||||
|
h("h4", null, "Roster"),
|
||||||
|
),
|
||||||
...members.map((member) => {
|
...members.map((member) => {
|
||||||
const canRemoveMember =
|
const canRemoveMember =
|
||||||
allowMemberManagement &&
|
allowMemberManagement &&
|
||||||
@ -76,7 +210,18 @@ ${scopeSelector} .org-name-row button {
|
|||||||
return h(
|
return h(
|
||||||
"article",
|
"article",
|
||||||
{ className: "org-name-row" },
|
{ className: "org-name-row" },
|
||||||
h("strong", null, member.name),
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "org-name-copy" },
|
||||||
|
h("strong", null, member.name),
|
||||||
|
member.uid
|
||||||
|
? h(
|
||||||
|
"span",
|
||||||
|
{ className: "org-name-meta" },
|
||||||
|
member.uid,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
canRemoveMember
|
canRemoveMember
|
||||||
? h(
|
? h(
|
||||||
"button",
|
"button",
|
||||||
|
|||||||
@ -15,8 +15,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const members = store.getMembers();
|
const members = store.getMembers();
|
||||||
|
const inviteablePlayers = store.getInviteablePlayers();
|
||||||
const memberSelectProps =
|
const memberSelectProps =
|
||||||
members.length === 0 ? { disabled: true } : {};
|
members.length === 0 ? { disabled: true } : {};
|
||||||
|
const inviteSelectProps =
|
||||||
|
inviteablePlayers.length === 0 ? { disabled: true } : {};
|
||||||
|
|
||||||
let title = "";
|
let title = "";
|
||||||
let body = null;
|
let body = null;
|
||||||
@ -211,6 +214,72 @@
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else if (modal.type === "invite") {
|
||||||
|
title = "Invite Player";
|
||||||
|
body = h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-form" },
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
null,
|
||||||
|
h("label", null, "Online Player"),
|
||||||
|
h(
|
||||||
|
"select",
|
||||||
|
{
|
||||||
|
id: "org-invite-player",
|
||||||
|
...inviteSelectProps,
|
||||||
|
},
|
||||||
|
...inviteablePlayers.map((player) =>
|
||||||
|
h(
|
||||||
|
"option",
|
||||||
|
{ value: player.uid },
|
||||||
|
player.name || player.uid,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
inviteablePlayers.length === 0
|
||||||
|
? h(
|
||||||
|
"p",
|
||||||
|
null,
|
||||||
|
"No eligible online players are currently available for invites.",
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{ className: "app-modal-actions" },
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
className: "org-secondary-btn",
|
||||||
|
onClick: () => actions.closeModal(),
|
||||||
|
},
|
||||||
|
"Cancel",
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
...inviteSelectProps,
|
||||||
|
onClick: () => {
|
||||||
|
if (
|
||||||
|
actions.sendInvite(
|
||||||
|
String(
|
||||||
|
actions.getInputValue(
|
||||||
|
"org-invite-player",
|
||||||
|
) || "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
actions.closeModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Send Invite",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else if (modal.type === "disband") {
|
} else if (modal.type === "disband") {
|
||||||
title = "Disband Organization";
|
title = "Disband Organization";
|
||||||
body = h(
|
body = h(
|
||||||
|
|||||||
@ -62,6 +62,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "invite" && !getters.canManageMembers()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can invite players.",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (type === "disband" && !getters.canDisbandOrg()) {
|
if (type === "disband" && !getters.canDisbandOrg()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -294,6 +302,77 @@
|
|||||||
amount,
|
amount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendInvite(targetUid) {
|
||||||
|
if (!getters.canManageMembers()) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Only the organization leader or CEO can invite players.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = store
|
||||||
|
.getInviteablePlayers()
|
||||||
|
.find((entry) => String(entry.uid || "") === String(targetUid));
|
||||||
|
|
||||||
|
if (!target) {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Select an online player to invite.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestInvitePlayer !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridge.requestInvitePlayer({
|
||||||
|
targetUid: String(target.uid || ""),
|
||||||
|
targetName: String(target.name || ""),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptInvite(orgId) {
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestAcceptInvite !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridge.requestAcceptInvite({ orgId });
|
||||||
|
}
|
||||||
|
|
||||||
|
declineInvite(orgId) {
|
||||||
|
const bridge = window.RegistryApp
|
||||||
|
? window.RegistryApp.bridge
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!bridge || typeof bridge.requestDeclineInvite !== "function") {
|
||||||
|
this.showTreasuryNotice(
|
||||||
|
"error",
|
||||||
|
"Organization invite bridge is unavailable.",
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridge.requestDeclineInvite({ orgId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OrgPortal.actions = new OrgPortalActions();
|
OrgPortal.actions = new OrgPortalActions();
|
||||||
|
|||||||
@ -74,6 +74,8 @@
|
|||||||
reputation: 0,
|
reputation: 0,
|
||||||
creditLines: [],
|
creditLines: [],
|
||||||
members: [],
|
members: [],
|
||||||
|
pendingInvites: [],
|
||||||
|
inviteablePlayers: [],
|
||||||
fleet: [],
|
fleet: [],
|
||||||
assets: [],
|
assets: [],
|
||||||
activity: [],
|
activity: [],
|
||||||
@ -126,6 +128,14 @@
|
|||||||
this.portalData.members,
|
this.portalData.members,
|
||||||
normalizeCollection(payload.portalData.members),
|
normalizeCollection(payload.portalData.members),
|
||||||
);
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.pendingInvites,
|
||||||
|
normalizeCollection(payload.portalData.pendingInvites),
|
||||||
|
);
|
||||||
|
replaceArray(
|
||||||
|
this.portalData.inviteablePlayers,
|
||||||
|
normalizeCollection(payload.portalData.inviteablePlayers),
|
||||||
|
);
|
||||||
replaceArray(
|
replaceArray(
|
||||||
this.portalData.fleet,
|
this.portalData.fleet,
|
||||||
normalizeCollection(payload.portalData.fleet),
|
normalizeCollection(payload.portalData.fleet),
|
||||||
|
|||||||
@ -51,6 +51,11 @@
|
|||||||
[this.getMembers, this.setMembers] = createSignal([
|
[this.getMembers, this.setMembers] = createSignal([
|
||||||
...portalData.members,
|
...portalData.members,
|
||||||
]);
|
]);
|
||||||
|
[this.getPendingInvites, this.setPendingInvites] = createSignal([
|
||||||
|
...portalData.pendingInvites,
|
||||||
|
]);
|
||||||
|
[this.getInviteablePlayers, this.setInviteablePlayers] =
|
||||||
|
createSignal([...portalData.inviteablePlayers]);
|
||||||
[this.getCreditLines, this.setCreditLines] = createSignal([
|
[this.getCreditLines, this.setCreditLines] = createSignal([
|
||||||
...portalData.creditLines,
|
...portalData.creditLines,
|
||||||
]);
|
]);
|
||||||
@ -77,6 +82,12 @@
|
|||||||
this.setFunds(nextPortalData.funds || 0);
|
this.setFunds(nextPortalData.funds || 0);
|
||||||
this.setReputation(nextPortalData.reputation || 0);
|
this.setReputation(nextPortalData.reputation || 0);
|
||||||
this.setMembers([...normalizeCollection(nextPortalData.members)]);
|
this.setMembers([...normalizeCollection(nextPortalData.members)]);
|
||||||
|
this.setPendingInvites([
|
||||||
|
...normalizeCollection(nextPortalData.pendingInvites),
|
||||||
|
]);
|
||||||
|
this.setInviteablePlayers([
|
||||||
|
...normalizeCollection(nextPortalData.inviteablePlayers),
|
||||||
|
]);
|
||||||
this.setCreditLines([
|
this.setCreditLines([
|
||||||
...normalizeCollection(nextPortalData.creditLines),
|
...normalizeCollection(nextPortalData.creditLines),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -85,6 +85,127 @@ PREP_RECOMPILE_END;
|
|||||||
]], _requester] call CFUNC(targetEvent);
|
]], _requester] call CFUNC(targetEvent);
|
||||||
}] call CFUNC(addEventHandler);
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestInviteOrgMember), {
|
||||||
|
params [["_uid", "", [""]], ["_targetUid", "", [""]], ["_targetName", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _targetUid isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid org invite request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requester = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_requester isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["inviteMember", [_uid, _targetUid, _targetName]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [createHashMap], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach [_uid, _result getOrDefault ["targetUid", _targetUid]];
|
||||||
|
|
||||||
|
private _targetPlayer = [_result getOrDefault ["targetUid", _targetUid]] call EFUNC(common,getPlayer);
|
||||||
|
if (_targetPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(notifications,recieveNotification), [
|
||||||
|
"info",
|
||||||
|
"Organization Invite",
|
||||||
|
"You received an organization invite. Open the organization portal to accept or decline it.",
|
||||||
|
7000
|
||||||
|
], _targetPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseInviteOrg), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to send organization invite."]]
|
||||||
|
]], _requester] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestAcceptOrgInvite), {
|
||||||
|
params [["_uid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid accept invite request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_player isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["acceptInvite", [_uid, _orgID]];
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
private _syncTargets = [_uid];
|
||||||
|
{
|
||||||
|
private _orgData = GVAR(OrgStore) call ["loadById", [_x]];
|
||||||
|
if !(_orgData isEqualType createHashMap) then { continue; };
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberUid = _y getOrDefault ["uid", ""];
|
||||||
|
if (_memberUid isNotEqualTo "") then {
|
||||||
|
_syncTargets pushBackUnique _memberUid;
|
||||||
|
};
|
||||||
|
} forEach (_orgData getOrDefault ["members", createHashMap]);
|
||||||
|
} forEach (_result getOrDefault ["affectedOrgIds", []]);
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [createHashMap], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach _syncTargets;
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseInviteDecision), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to accept organization invite."]],
|
||||||
|
["action", "accept"]
|
||||||
|
]], _player] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
|
[QGVAR(requestDeclineOrgInvite), {
|
||||||
|
params [["_uid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
diag_log "[FORGE:Server:Org] Invalid decline invite request payload!"
|
||||||
|
};
|
||||||
|
|
||||||
|
private _player = [_uid] call EFUNC(common,getPlayer);
|
||||||
|
if (_player isEqualTo objNull) exitWith {};
|
||||||
|
|
||||||
|
private _result = GVAR(OrgStore) call ["declineInvite", [_uid, _orgID]];
|
||||||
|
if (_result getOrDefault ["success", false]) then {
|
||||||
|
private _syncTargets = [_uid];
|
||||||
|
{
|
||||||
|
private _orgData = GVAR(OrgStore) call ["loadById", [_x]];
|
||||||
|
if !(_orgData isEqualType createHashMap) then { continue; };
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberUid = _y getOrDefault ["uid", ""];
|
||||||
|
if (_memberUid isNotEqualTo "") then {
|
||||||
|
_syncTargets pushBackUnique _memberUid;
|
||||||
|
};
|
||||||
|
} forEach (_orgData getOrDefault ["members", createHashMap]);
|
||||||
|
} forEach (_result getOrDefault ["affectedOrgIds", []]);
|
||||||
|
|
||||||
|
{
|
||||||
|
private _memberPlayer = [_x] call EFUNC(common,getPlayer);
|
||||||
|
if (_memberPlayer isNotEqualTo objNull) then {
|
||||||
|
[CRPC(org,responseSyncOrg), [createHashMap], _memberPlayer] call CFUNC(targetEvent);
|
||||||
|
};
|
||||||
|
} forEach _syncTargets;
|
||||||
|
};
|
||||||
|
|
||||||
|
[CRPC(org,responseInviteDecision), [createHashMapFromArray [
|
||||||
|
["success", _result getOrDefault ["success", false]],
|
||||||
|
["message", _result getOrDefault ["message", "Unable to decline organization invite."]],
|
||||||
|
["action", "decline"]
|
||||||
|
]], _player] call CFUNC(targetEvent);
|
||||||
|
}] call CFUNC(addEventHandler);
|
||||||
|
|
||||||
[QGVAR(requestLeaveOrg), {
|
[QGVAR(requestLeaveOrg), {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
|
|||||||
_org set ["assets", createHashMap];
|
_org set ["assets", createHashMap];
|
||||||
_org set ["fleet", createHashMap];
|
_org set ["fleet", createHashMap];
|
||||||
_org set ["members", createHashMap];
|
_org set ["members", createHashMap];
|
||||||
|
_org set ["pending_invites", createHashMap];
|
||||||
|
|
||||||
_org
|
_org
|
||||||
}],
|
}],
|
||||||
@ -111,6 +112,13 @@ GVAR(OrgModel) = compileFinal createHashMapObject [[
|
|||||||
|
|
||||||
_org set ["credit_lines", _creditLines];
|
_org set ["credit_lines", _creditLines];
|
||||||
|
|
||||||
|
private _pendingInvites = _org getOrDefault ["pending_invites", createHashMap];
|
||||||
|
if !(_pendingInvites isEqualType createHashMap) then {
|
||||||
|
_pendingInvites = createHashMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
_org set ["pending_invites", _pendingInvites];
|
||||||
|
|
||||||
_org
|
_org
|
||||||
}],
|
}],
|
||||||
["validate", compileFinal {
|
["validate", compileFinal {
|
||||||
@ -158,7 +166,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["credit_lines", createHashMap],
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap],
|
||||||
|
["pending_invites", createHashMap]
|
||||||
];
|
];
|
||||||
_defaultOrg
|
_defaultOrg
|
||||||
};
|
};
|
||||||
@ -173,7 +182,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["credit_lines", createHashMap],
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap],
|
||||||
|
["pending_invites", createHashMap]
|
||||||
];
|
];
|
||||||
|
|
||||||
private _defaultJson = _self call ["toJSON", [_defaultOrg]];
|
private _defaultJson = _self call ["toJSON", [_defaultOrg]];
|
||||||
@ -191,7 +201,8 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
["credit_lines", createHashMap],
|
["credit_lines", createHashMap],
|
||||||
["assets", createHashMap],
|
["assets", createHashMap],
|
||||||
["fleet", createHashMap],
|
["fleet", createHashMap],
|
||||||
["members", createHashMap]
|
["members", createHashMap],
|
||||||
|
["pending_invites", createHashMap]
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -240,6 +251,24 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_data
|
_data
|
||||||
}],
|
}],
|
||||||
|
["callHotOrgArray", compileFinal {
|
||||||
|
params [["_function", "", [""]], ["_arguments", [], [[]]]];
|
||||||
|
|
||||||
|
if (_function isEqualTo "") exitWith { [] };
|
||||||
|
|
||||||
|
[_function, _arguments] call EFUNC(extension,extCall) params ["_result", "_isSuccess"];
|
||||||
|
if !(_isSuccess) exitWith { [] };
|
||||||
|
if !(_result isEqualType "") exitWith { [] };
|
||||||
|
if ((_result find "Error:") == 0) exitWith {
|
||||||
|
["ERROR", format ["Org extension call '%1' failed: %2", _function, _result]] call EFUNC(common,log);
|
||||||
|
[]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _data = fromJSON _result;
|
||||||
|
if !(_data isEqualType []) exitWith { [] };
|
||||||
|
|
||||||
|
_data
|
||||||
|
}],
|
||||||
["syncHotOrg", compileFinal {
|
["syncHotOrg", compileFinal {
|
||||||
params [["_org", createHashMap, [createHashMap]]];
|
params [["_org", createHashMap, [createHashMap]]];
|
||||||
|
|
||||||
@ -356,6 +385,203 @@ GVAR(OrgBaseStore) = compileFinal createHashMapFromArray [
|
|||||||
|
|
||||||
_self call ["callHotOrg", ["org:hot:ensure_member", [toJSON _context]]]
|
_self call ["callHotOrg", ["org:hot:ensure_member", [toJSON _context]]]
|
||||||
}],
|
}],
|
||||||
|
["listMemberInvites", compileFinal {
|
||||||
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
if (_uid isEqualTo "") exitWith { [] };
|
||||||
|
|
||||||
|
_self call ["callHotOrgArray", ["org:hot:member_invites", [_uid]]]
|
||||||
|
}],
|
||||||
|
["inviteMember", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_targetUid", "", [""]], ["_targetName", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["targetUid", _targetUid],
|
||||||
|
["persisted", false],
|
||||||
|
["persistenceMessage", ""]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_requesterUid isEqualTo "" || { _targetUid isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid organization invite target is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _requesterActor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
|
private _requesterOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
|
private _requesterName = _self call ["resolveActorName", [_requesterUid, _requesterPlayer, _requesterActor]];
|
||||||
|
private _requesterIsDefaultOrgCeo = (
|
||||||
|
_requesterPlayer isNotEqualTo objNull
|
||||||
|
&& { _requesterOrgID isEqualTo "default" }
|
||||||
|
&& { toLowerANSI (vehicleVarName _requesterPlayer) isEqualTo "ceo" }
|
||||||
|
);
|
||||||
|
private _targetOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_targetUid]];
|
||||||
|
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["requesterName", _requesterName],
|
||||||
|
["orgId", _requesterOrgID],
|
||||||
|
["requesterIsDefaultOrgCeo", _requesterIsDefaultOrgCeo],
|
||||||
|
["targetUid", _targetUid],
|
||||||
|
["targetName", _targetName],
|
||||||
|
["targetOrgId", _targetOrgID]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _envelope = _self call ["callHotOrgEnvelope", ["org:hot:invite_member", [toJSON _context]]];
|
||||||
|
if (_envelope isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Unable to send organization invite."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _envelope getOrDefault ["message", "Invitation sent."]];
|
||||||
|
_result set ["targetUid", _envelope getOrDefault ["targetUid", _targetUid]];
|
||||||
|
_self call ["persistMutationResult", [_requesterOrgID, _result, "Organization invite"]]
|
||||||
|
}],
|
||||||
|
["acceptInvite", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["actorPatch", createHashMap],
|
||||||
|
["affectedOrgIds", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_requesterUid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid invite selection is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _requesterActor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
|
private _requesterName = _self call ["resolveActorName", [_requesterUid, _requesterPlayer, _requesterActor]];
|
||||||
|
private _existingOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["requesterName", _requesterName],
|
||||||
|
["orgId", _orgID],
|
||||||
|
["existingOrgId", _existingOrgID]
|
||||||
|
];
|
||||||
|
|
||||||
|
["org:hot:accept_invite", [toJSON _context]] call EFUNC(extension,extCall) params ["_rawResult", "_isSuccess"];
|
||||||
|
if !_isSuccess exitWith {
|
||||||
|
_result set ["message", "Organization invite service was unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if !(_rawResult isEqualType "") exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned an invalid response."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if ((_rawResult find "Error:") == 0) exitWith {
|
||||||
|
_result set ["message", _rawResult select [7]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _envelope = fromJSON _rawResult;
|
||||||
|
if !(_envelope isEqualType createHashMap) exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned malformed data."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _invitedOrg = _self call ["syncHotOrg", [_envelope getOrDefault ["invitedOrg", createHashMap]]];
|
||||||
|
if (_invitedOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["invitedOrg", _invitedOrg];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _previousOrgData = _envelope getOrDefault ["previousOrg", createHashMap];
|
||||||
|
if (_previousOrgData isEqualType createHashMap && { _previousOrgData isNotEqualTo createHashMap }) then {
|
||||||
|
private _syncedPreviousOrg = _self call ["syncHotOrg", [_previousOrgData]];
|
||||||
|
if (_syncedPreviousOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["previousOrg", _syncedPreviousOrg];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _actorOrg = _envelope getOrDefault ["actorOrganization", _orgID];
|
||||||
|
private _actorPatch = _self call ["applyActorOrganization", [_requesterUid, _actorOrg, _requesterActor]];
|
||||||
|
if (_actorPatch isEqualTo createHashMap) exitWith {
|
||||||
|
_result set ["message", "Failed to assign the player to the invited organization."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _affectedOrgIds = [_actorOrg];
|
||||||
|
private _previousOrg = _envelope getOrDefault ["previousOrg", createHashMap];
|
||||||
|
if (_previousOrg isEqualType createHashMap && { _previousOrg isNotEqualTo createHashMap }) then {
|
||||||
|
private _previousOrgID = _previousOrg getOrDefault ["id", ""];
|
||||||
|
if (_previousOrgID isNotEqualTo "") then {
|
||||||
|
_affectedOrgIds pushBackUnique _previousOrgID;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
_self call ["saveById", [_x]];
|
||||||
|
} forEach _affectedOrgIds;
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _envelope getOrDefault ["message", "Organization invite accepted."]];
|
||||||
|
_result set ["actorPatch", _actorPatch];
|
||||||
|
_result set ["affectedOrgIds", _affectedOrgIds];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
|
["declineInvite", compileFinal {
|
||||||
|
params [["_requesterUid", "", [""]], ["_orgID", "", [""]]];
|
||||||
|
|
||||||
|
private _result = createHashMapFromArray [
|
||||||
|
["success", false],
|
||||||
|
["message", ""],
|
||||||
|
["affectedOrgIds", []]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (_requesterUid isEqualTo "" || { _orgID isEqualTo "" }) exitWith {
|
||||||
|
_result set ["message", "A valid invite selection is required."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _requesterPlayer = [_requesterUid] call EFUNC(common,getPlayer);
|
||||||
|
private _requesterActor = EGVAR(actor,ActorStore) call ["load", [_requesterUid]];
|
||||||
|
private _requesterName = _self call ["resolveActorName", [_requesterUid, _requesterPlayer, _requesterActor]];
|
||||||
|
private _existingOrgID = EGVAR(actor,ActorStore) call ["getOrganization", [_requesterUid]];
|
||||||
|
private _context = createHashMapFromArray [
|
||||||
|
["requesterUid", _requesterUid],
|
||||||
|
["requesterName", _requesterName],
|
||||||
|
["orgId", _orgID],
|
||||||
|
["existingOrgId", _existingOrgID]
|
||||||
|
];
|
||||||
|
|
||||||
|
["org:hot:decline_invite", [toJSON _context]] call EFUNC(extension,extCall) params ["_rawResult", "_isSuccess"];
|
||||||
|
if !_isSuccess exitWith {
|
||||||
|
_result set ["message", "Organization invite service was unavailable."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if !(_rawResult isEqualType "") exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned an invalid response."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
if ((_rawResult find "Error:") == 0) exitWith {
|
||||||
|
_result set ["message", _rawResult select [7]];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _envelope = fromJSON _rawResult;
|
||||||
|
if !(_envelope isEqualType createHashMap) exitWith {
|
||||||
|
_result set ["message", "Organization invite service returned malformed data."];
|
||||||
|
_result
|
||||||
|
};
|
||||||
|
|
||||||
|
private _invitedOrg = _self call ["syncHotOrg", [_envelope getOrDefault ["invitedOrg", createHashMap]]];
|
||||||
|
if (_invitedOrg isNotEqualTo createHashMap) then {
|
||||||
|
_envelope set ["invitedOrg", _invitedOrg];
|
||||||
|
};
|
||||||
|
|
||||||
|
_self call ["saveById", [_orgID]];
|
||||||
|
|
||||||
|
_result set ["success", true];
|
||||||
|
_result set ["message", _envelope getOrDefault ["message", "Organization invite declined."]];
|
||||||
|
_result set ["affectedOrgIds", [_orgID]];
|
||||||
|
_result
|
||||||
|
}],
|
||||||
["leave", compileFinal {
|
["leave", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
|
|||||||
@ -152,6 +152,64 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
|
|
||||||
_creditLinesList
|
_creditLinesList
|
||||||
}],
|
}],
|
||||||
|
["buildPendingInvitesList", compileFinal {
|
||||||
|
params [["_pendingInvitesRaw", [], [[]]]];
|
||||||
|
|
||||||
|
private _pendingInvites = [];
|
||||||
|
{
|
||||||
|
if !(_x isEqualType createHashMap) then { continue; };
|
||||||
|
|
||||||
|
_pendingInvites pushBack [
|
||||||
|
["orgId", _x getOrDefault ["orgId", ""]],
|
||||||
|
["orgName", _x getOrDefault ["orgName", "Unknown Organization"]],
|
||||||
|
["inviterUid", _x getOrDefault ["inviterUid", ""]],
|
||||||
|
["inviterName", _x getOrDefault ["inviterName", "Unknown"]],
|
||||||
|
["targetUid", _x getOrDefault ["targetUid", ""]],
|
||||||
|
["targetName", _x getOrDefault ["targetName", "Unknown"]]
|
||||||
|
];
|
||||||
|
} forEach _pendingInvitesRaw;
|
||||||
|
|
||||||
|
_pendingInvites
|
||||||
|
}],
|
||||||
|
["buildInviteablePlayers", compileFinal {
|
||||||
|
params [
|
||||||
|
["_uid", "", [""]],
|
||||||
|
["_orgID", "", [""]],
|
||||||
|
["_membersRaw", createHashMap, [createHashMap]],
|
||||||
|
["_pendingInvitesRaw", createHashMap, [createHashMap]]
|
||||||
|
];
|
||||||
|
|
||||||
|
private _memberUids = [];
|
||||||
|
{
|
||||||
|
_memberUids pushBackUnique (_y getOrDefault ["uid", ""]);
|
||||||
|
} forEach _membersRaw;
|
||||||
|
|
||||||
|
private _pendingInviteUids = [];
|
||||||
|
{
|
||||||
|
_pendingInviteUids pushBackUnique (_x);
|
||||||
|
} forEach _pendingInvitesRaw;
|
||||||
|
|
||||||
|
private _players = [];
|
||||||
|
{
|
||||||
|
private _player = _x;
|
||||||
|
if (isNull _player) then { continue; };
|
||||||
|
|
||||||
|
private _playerUid = getPlayerUID _player;
|
||||||
|
if (_playerUid isEqualTo "" || { _playerUid isEqualTo _uid }) then { continue; };
|
||||||
|
if (_playerUid in _memberUids || { _playerUid in _pendingInviteUids }) then { continue; };
|
||||||
|
|
||||||
|
private _playerOrgID = GVAR(OrgStore) call ["resolveOrgIdForUid", [_playerUid]];
|
||||||
|
if (_playerOrgID isNotEqualTo "default") then { continue; };
|
||||||
|
|
||||||
|
_players pushBack [
|
||||||
|
["uid", _playerUid],
|
||||||
|
["name", name _player],
|
||||||
|
["orgId", _playerOrgID]
|
||||||
|
];
|
||||||
|
} forEach allPlayers;
|
||||||
|
|
||||||
|
_players
|
||||||
|
}],
|
||||||
["buildPortalPayload", compileFinal {
|
["buildPortalPayload", compileFinal {
|
||||||
params [["_uid", "", [""]]];
|
params [["_uid", "", [""]]];
|
||||||
|
|
||||||
@ -177,9 +235,14 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
private _assetsRaw = _org getOrDefault ["assets", createHashMap];
|
private _assetsRaw = _org getOrDefault ["assets", createHashMap];
|
||||||
private _fleetRaw = _org getOrDefault ["fleet", createHashMap];
|
private _fleetRaw = _org getOrDefault ["fleet", createHashMap];
|
||||||
private _membersRaw = _org getOrDefault ["members", createHashMap];
|
private _membersRaw = _org getOrDefault ["members", createHashMap];
|
||||||
|
private _pendingInvitesRaw = _org getOrDefault ["pending_invites", createHashMap];
|
||||||
private _isDefaultOrg = (_org getOrDefault ["default", false])
|
private _isDefaultOrg = (_org getOrDefault ["default", false])
|
||||||
|| { toLowerANSI _id isEqualTo "default" }
|
|| { toLowerANSI _id isEqualTo "default" }
|
||||||
|| { toLowerANSI _ownerUid isEqualTo "server" };
|
|| { toLowerANSI _ownerUid isEqualTo "server" };
|
||||||
|
private _memberInvites = [];
|
||||||
|
if (_isDefaultOrg) then {
|
||||||
|
_memberInvites = GVAR(OrgStore) call ["listMemberInvites", [_uid]];
|
||||||
|
};
|
||||||
|
|
||||||
private _playerName = name _player;
|
private _playerName = name _player;
|
||||||
private _playerVar = vehicleVarName _player;
|
private _playerVar = vehicleVarName _player;
|
||||||
@ -209,6 +272,8 @@ GVAR(OrgPayloadBuilder) = createHashMapObject [[
|
|||||||
["reputation", _reputation],
|
["reputation", _reputation],
|
||||||
["creditLines", _self call ["buildCreditLinesList", [_creditLinesRaw]]],
|
["creditLines", _self call ["buildCreditLinesList", [_creditLinesRaw]]],
|
||||||
["members", _memberShape getOrDefault ["members", []]],
|
["members", _memberShape getOrDefault ["members", []]],
|
||||||
|
["pendingInvites", _self call ["buildPendingInvitesList", [_memberInvites]]],
|
||||||
|
["inviteablePlayers", _self call ["buildInviteablePlayers", [_uid, _id, _membersRaw, _pendingInvitesRaw]]],
|
||||||
["fleet", _self call ["buildFleetList", [_fleetRaw]]],
|
["fleet", _self call ["buildFleetList", [_fleetRaw]]],
|
||||||
["assets", _self call ["buildAssetsList", [_assetsRaw]]],
|
["assets", _self call ["buildAssetsList", [_assetsRaw]]],
|
||||||
["activity", []]
|
["activity", []]
|
||||||
|
|||||||
@ -7,8 +7,9 @@ use arma_rs::Group;
|
|||||||
use forge_models::{
|
use forge_models::{
|
||||||
HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
HotOrgRecord, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
||||||
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandResult,
|
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandResult,
|
||||||
OrgEnsureMemberContext, OrgFleetGrantSeed, OrgGrantContext, OrgLeaveContext, OrgLeaveResult,
|
OrgEnsureMemberContext, OrgFleetGrantSeed, OrgGrantContext, OrgInviteContext,
|
||||||
OrgRegisterContext,
|
OrgInviteDecisionContext, OrgInviteDecisionResult, OrgInviteRecord, OrgInviteResult,
|
||||||
|
OrgLeaveContext, OrgLeaveResult, OrgRegisterContext,
|
||||||
};
|
};
|
||||||
use forge_repositories::{InMemoryOrgHotRepository, RedisOrgRepository};
|
use forge_repositories::{InMemoryOrgHotRepository, RedisOrgRepository};
|
||||||
use forge_services::{OrgHotStateService, OrgService};
|
use forge_services::{OrgHotStateService, OrgService};
|
||||||
@ -58,7 +59,11 @@ pub fn group() -> Group {
|
|||||||
.command("get", get_hot_org)
|
.command("get", get_hot_org)
|
||||||
.command("override", override_hot_org)
|
.command("override", override_hot_org)
|
||||||
.command("ensure_member", ensure_hot_org_member)
|
.command("ensure_member", ensure_hot_org_member)
|
||||||
|
.command("member_invites", get_hot_org_member_invites)
|
||||||
.command("register", register_hot_org)
|
.command("register", register_hot_org)
|
||||||
|
.command("invite_member", invite_hot_org_member)
|
||||||
|
.command("accept_invite", accept_hot_org_invite)
|
||||||
|
.command("decline_invite", decline_hot_org_invite)
|
||||||
.command("assign_credit_line", assign_credit_line_hot_org)
|
.command("assign_credit_line", assign_credit_line_hot_org)
|
||||||
.command("repay_credit_line", repay_credit_line_hot_org)
|
.command("repay_credit_line", repay_credit_line_hot_org)
|
||||||
.command("charge_checkout", charge_checkout_hot_org)
|
.command("charge_checkout", charge_checkout_hot_org)
|
||||||
@ -142,6 +147,13 @@ pub(crate) fn ensure_hot_org_member(json_data: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_hot_org_member_invites(member_uid: String) -> String {
|
||||||
|
match HOT_ORG_SERVICE.get_member_invites(member_uid) {
|
||||||
|
Ok(invites) => serialize_result::<Vec<OrgInviteRecord>>(&invites, "org invite list"),
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn register_hot_org(json_data: String) -> String {
|
pub(crate) fn register_hot_org(json_data: String) -> String {
|
||||||
let context: OrgRegisterContext = match serde_json::from_str(&json_data) {
|
let context: OrgRegisterContext = match serde_json::from_str(&json_data) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
@ -154,6 +166,46 @@ pub(crate) fn register_hot_org(json_data: String) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn invite_hot_org_member(json_data: String) -> String {
|
||||||
|
let context: OrgInviteContext = match serde_json::from_str(&json_data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(error) => return format!("Error: Invalid org invite JSON: {}", error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match HOT_ORG_SERVICE.invite_member(context) {
|
||||||
|
Ok(result) => serialize_result::<OrgInviteResult>(&result, "org invite result"),
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn accept_hot_org_invite(json_data: String) -> String {
|
||||||
|
let context: OrgInviteDecisionContext = match serde_json::from_str(&json_data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(error) => return format!("Error: Invalid org invite decision JSON: {}", error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match HOT_ORG_SERVICE.accept_invite(context) {
|
||||||
|
Ok(result) => {
|
||||||
|
serialize_result::<OrgInviteDecisionResult>(&result, "org invite decision result")
|
||||||
|
}
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decline_hot_org_invite(json_data: String) -> String {
|
||||||
|
let context: OrgInviteDecisionContext = match serde_json::from_str(&json_data) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(error) => return format!("Error: Invalid org invite decision JSON: {}", error),
|
||||||
|
};
|
||||||
|
|
||||||
|
match HOT_ORG_SERVICE.decline_invite(context) {
|
||||||
|
Ok(result) => {
|
||||||
|
serialize_result::<OrgInviteDecisionResult>(&result, "org invite decision result")
|
||||||
|
}
|
||||||
|
Err(error) => format!("Error: {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn assign_credit_line_hot_org(json_data: String) -> String {
|
pub(crate) fn assign_credit_line_hot_org(json_data: String) -> String {
|
||||||
let context: OrgCreditLineContext = match serde_json::from_str(&json_data) {
|
let context: OrgCreditLineContext = match serde_json::from_str(&json_data) {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
|
|||||||
@ -359,10 +359,26 @@ fn route_command(
|
|||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(org::ensure_hot_org_member(arguments[0].clone()))
|
Ok(org::ensure_hot_org_member(arguments[0].clone()))
|
||||||
}
|
}
|
||||||
|
"org:hot:member_invites" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::get_hot_org_member_invites(arguments[0].clone()))
|
||||||
|
}
|
||||||
"org:hot:register" => {
|
"org:hot:register" => {
|
||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(org::register_hot_org(arguments[0].clone()))
|
Ok(org::register_hot_org(arguments[0].clone()))
|
||||||
}
|
}
|
||||||
|
"org:hot:invite_member" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::invite_hot_org_member(arguments[0].clone()))
|
||||||
|
}
|
||||||
|
"org:hot:accept_invite" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::accept_hot_org_invite(arguments[0].clone()))
|
||||||
|
}
|
||||||
|
"org:hot:decline_invite" => {
|
||||||
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
|
Ok(org::decline_hot_org_invite(arguments[0].clone()))
|
||||||
|
}
|
||||||
"org:hot:assign_credit_line" => {
|
"org:hot:assign_credit_line" => {
|
||||||
expect_arg_count(function_name, &arguments, 1)?;
|
expect_arg_count(function_name, &arguments, 1)?;
|
||||||
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
|
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
|
||||||
|
|||||||
@ -27,7 +27,9 @@ pub use org::{
|
|||||||
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
||||||
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
||||||
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
||||||
OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult,
|
OrgInviteContext, OrgInviteDecisionContext, OrgInviteDecisionResult, OrgInviteRecord,
|
||||||
|
OrgInviteResult, OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext,
|
||||||
|
OrgRegisterResult,
|
||||||
};
|
};
|
||||||
pub use store::{
|
pub use store::{
|
||||||
StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed,
|
StoreCheckoutContext, StoreCheckoutItemSeed, StoreCheckoutResult, StoreCheckoutVehicleSeed,
|
||||||
|
|||||||
@ -69,6 +69,17 @@ pub struct MemberSummary {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteRecord {
|
||||||
|
pub org_id: String,
|
||||||
|
pub org_name: String,
|
||||||
|
pub inviter_uid: String,
|
||||||
|
pub inviter_name: String,
|
||||||
|
pub target_uid: String,
|
||||||
|
pub target_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct HotOrgRecord {
|
pub struct HotOrgRecord {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@ -84,6 +95,8 @@ pub struct HotOrgRecord {
|
|||||||
pub fleet: HashMap<String, OrgFleetEntry>,
|
pub fleet: HashMap<String, OrgFleetEntry>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub members: HashMap<String, MemberSummary>,
|
pub members: HashMap<String, MemberSummary>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pending_invites: HashMap<String, OrgInviteRecord>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@ -112,6 +125,44 @@ pub struct OrgRegisterResult {
|
|||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteContext {
|
||||||
|
pub requester_uid: String,
|
||||||
|
pub requester_name: String,
|
||||||
|
pub org_id: String,
|
||||||
|
pub requester_is_default_org_ceo: bool,
|
||||||
|
pub target_uid: String,
|
||||||
|
pub target_name: String,
|
||||||
|
pub target_org_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteDecisionContext {
|
||||||
|
pub requester_uid: String,
|
||||||
|
pub requester_name: String,
|
||||||
|
pub org_id: String,
|
||||||
|
pub existing_org_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteResult {
|
||||||
|
pub org: HotOrgRecord,
|
||||||
|
pub target_uid: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrgInviteDecisionResult {
|
||||||
|
pub invited_org: HotOrgRecord,
|
||||||
|
pub previous_org: Option<HotOrgRecord>,
|
||||||
|
pub actor_organization: String,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OrgCreditLineContext {
|
pub struct OrgCreditLineContext {
|
||||||
@ -329,6 +380,7 @@ impl HotOrgRecord {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|member| (member.uid.clone(), member))
|
.map(|member| (member.uid.clone(), member))
|
||||||
.collect(),
|
.collect(),
|
||||||
|
pending_invites: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@ pub trait OrgRepository: Send + Sync {
|
|||||||
|
|
||||||
pub trait OrgHotRepository: Send + Sync {
|
pub trait OrgHotRepository: Send + Sync {
|
||||||
fn get(&self, id: &str) -> Result<Option<HotOrgRecord>, String>;
|
fn get(&self, id: &str) -> Result<Option<HotOrgRecord>, String>;
|
||||||
|
fn keys(&self) -> Result<Vec<String>, String>;
|
||||||
fn save(&self, org: &HotOrgRecord) -> Result<(), String>;
|
fn save(&self, org: &HotOrgRecord) -> Result<(), String>;
|
||||||
fn delete(&self, id: &str) -> Result<(), String>;
|
fn delete(&self, id: &str) -> Result<(), String>;
|
||||||
}
|
}
|
||||||
@ -89,6 +90,13 @@ impl OrgHotRepository for InMemoryOrgHotRepository {
|
|||||||
.map_err(|_| "Org hot state lock poisoned.".to_string())
|
.map_err(|_| "Org hot state lock poisoned.".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keys(&self) -> Result<Vec<String>, String> {
|
||||||
|
self.state
|
||||||
|
.read()
|
||||||
|
.map(|state| state.keys().cloned().collect())
|
||||||
|
.map_err(|_| "Org hot state lock poisoned.".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn save(&self, org: &HotOrgRecord) -> Result<(), String> {
|
fn save(&self, org: &HotOrgRecord) -> Result<(), String> {
|
||||||
self.state
|
self.state
|
||||||
.write()
|
.write()
|
||||||
|
|||||||
@ -10,7 +10,9 @@ use forge_models::{
|
|||||||
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
OrgAssetEntry, OrgAssetGrantSeed, OrgCheckoutContext, OrgCreditLineContext,
|
||||||
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
OrgCreditLineRepaymentContext, OrgCreditLineRepaymentResult, OrgDisbandMemberResult,
|
||||||
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
OrgDisbandResult, OrgEnsureMemberContext, OrgFleetEntry, OrgFleetGrantSeed, OrgGrantContext,
|
||||||
OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext, OrgRegisterResult,
|
OrgInviteContext, OrgInviteDecisionContext, OrgInviteDecisionResult, OrgInviteRecord,
|
||||||
|
OrgInviteResult, OrgLeaveContext, OrgLeaveResult, OrgMutationResult, OrgRegisterContext,
|
||||||
|
OrgRegisterResult,
|
||||||
};
|
};
|
||||||
use forge_repositories::{OrgHotRepository, OrgRepository};
|
use forge_repositories::{OrgHotRepository, OrgRepository};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
@ -365,6 +367,26 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
|||||||
self.init_org(id)
|
self.init_org(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_member_invites(&self, member_uid: String) -> Result<Vec<OrgInviteRecord>, String> {
|
||||||
|
if member_uid.trim().is_empty() {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invites = Vec::new();
|
||||||
|
for org_id in self.repository.keys()? {
|
||||||
|
let Some(org) = self.repository.get(&org_id)? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(invite) = org.pending_invites.get(&member_uid) {
|
||||||
|
invites.push(invite.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invites.sort_by(|left, right| left.org_name.cmp(&right.org_name));
|
||||||
|
Ok(invites)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn override_org(
|
pub fn override_org(
|
||||||
&self,
|
&self,
|
||||||
id: String,
|
id: String,
|
||||||
@ -512,6 +534,162 @@ impl<R: OrgRepository, H: OrgHotRepository> OrgHotStateService<R, H> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn invite_member(&self, context: OrgInviteContext) -> Result<OrgInviteResult, String> {
|
||||||
|
if context.requester_uid.trim().is_empty()
|
||||||
|
|| context.target_uid.trim().is_empty()
|
||||||
|
|| context.org_id.trim().is_empty()
|
||||||
|
{
|
||||||
|
return Err("A valid organization invite request is required.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut org = self.get_org(context.org_id.clone())?;
|
||||||
|
if !can_manage_treasury(
|
||||||
|
&org,
|
||||||
|
&context.requester_uid,
|
||||||
|
context.requester_is_default_org_ceo,
|
||||||
|
) {
|
||||||
|
return Err(
|
||||||
|
"Only the organization leader or CEO can send organization invites.".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if context.target_uid == context.requester_uid {
|
||||||
|
return Err("You cannot invite yourself to the organization.".to_string());
|
||||||
|
}
|
||||||
|
if org.members.contains_key(&context.target_uid) {
|
||||||
|
return Err("Selected player is already a member of this organization.".to_string());
|
||||||
|
}
|
||||||
|
if !context.target_org_id.trim().is_empty()
|
||||||
|
&& !context.target_org_id.eq_ignore_ascii_case("default")
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"Selected player must leave their current organization before joining another."
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_name = if context.target_name.trim().is_empty() {
|
||||||
|
"Unknown".to_string()
|
||||||
|
} else {
|
||||||
|
context.target_name.clone()
|
||||||
|
};
|
||||||
|
let inviter_name = if context.requester_name.trim().is_empty() {
|
||||||
|
"Unknown".to_string()
|
||||||
|
} else {
|
||||||
|
context.requester_name.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
org.pending_invites.insert(
|
||||||
|
context.target_uid.clone(),
|
||||||
|
OrgInviteRecord {
|
||||||
|
org_id: org.id.clone(),
|
||||||
|
org_name: org.name.clone(),
|
||||||
|
inviter_uid: context.requester_uid,
|
||||||
|
inviter_name,
|
||||||
|
target_uid: context.target_uid.clone(),
|
||||||
|
target_name: target_name.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.repository.save(&org)?;
|
||||||
|
|
||||||
|
Ok(OrgInviteResult {
|
||||||
|
org,
|
||||||
|
target_uid: context.target_uid,
|
||||||
|
message: format!("Invitation sent to {}.", target_name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept_invite(
|
||||||
|
&self,
|
||||||
|
context: OrgInviteDecisionContext,
|
||||||
|
) -> Result<OrgInviteDecisionResult, String> {
|
||||||
|
if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() {
|
||||||
|
return Err("A valid organization invite acceptance is required.".to_string());
|
||||||
|
}
|
||||||
|
if !context.existing_org_id.trim().is_empty()
|
||||||
|
&& !context.existing_org_id.eq_ignore_ascii_case("default")
|
||||||
|
&& !context
|
||||||
|
.existing_org_id
|
||||||
|
.eq_ignore_ascii_case(&context.org_id)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"Leave your current organization before accepting another invite.".to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invited_org = self.get_org(context.org_id.clone())?;
|
||||||
|
let invite = invited_org
|
||||||
|
.pending_invites
|
||||||
|
.remove(&context.requester_uid)
|
||||||
|
.ok_or_else(|| "That organization invite is no longer available.".to_string())?;
|
||||||
|
|
||||||
|
if invited_org.members.contains_key(&context.requester_uid) {
|
||||||
|
self.repository.save(&invited_org)?;
|
||||||
|
return Ok(OrgInviteDecisionResult {
|
||||||
|
previous_org: None,
|
||||||
|
actor_organization: invited_org.id.clone(),
|
||||||
|
message: "You are already a member of that organization.".to_string(),
|
||||||
|
invited_org,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let requester_name = if context.requester_name.trim().is_empty() {
|
||||||
|
invite.target_name
|
||||||
|
} else {
|
||||||
|
context.requester_name
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut previous_org = None;
|
||||||
|
if !context.existing_org_id.trim().is_empty()
|
||||||
|
&& !context
|
||||||
|
.existing_org_id
|
||||||
|
.eq_ignore_ascii_case(&invited_org.id)
|
||||||
|
{
|
||||||
|
let mut current_org = self.init_org(context.existing_org_id.clone())?;
|
||||||
|
current_org.members.remove(&context.requester_uid);
|
||||||
|
self.repository.save(¤t_org)?;
|
||||||
|
previous_org = Some(current_org);
|
||||||
|
}
|
||||||
|
|
||||||
|
invited_org.members.insert(
|
||||||
|
context.requester_uid.clone(),
|
||||||
|
MemberSummary {
|
||||||
|
uid: context.requester_uid,
|
||||||
|
name: requester_name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.repository.save(&invited_org)?;
|
||||||
|
|
||||||
|
Ok(OrgInviteDecisionResult {
|
||||||
|
previous_org,
|
||||||
|
actor_organization: invited_org.id.clone(),
|
||||||
|
message: format!("You joined {}.", invited_org.name),
|
||||||
|
invited_org,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decline_invite(
|
||||||
|
&self,
|
||||||
|
context: OrgInviteDecisionContext,
|
||||||
|
) -> Result<OrgInviteDecisionResult, String> {
|
||||||
|
if context.requester_uid.trim().is_empty() || context.org_id.trim().is_empty() {
|
||||||
|
return Err("A valid organization invite decline is required.".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut invited_org = self.get_org(context.org_id.clone())?;
|
||||||
|
let invite = invited_org
|
||||||
|
.pending_invites
|
||||||
|
.remove(&context.requester_uid)
|
||||||
|
.ok_or_else(|| "That organization invite is no longer available.".to_string())?;
|
||||||
|
self.repository.save(&invited_org)?;
|
||||||
|
|
||||||
|
Ok(OrgInviteDecisionResult {
|
||||||
|
previous_org: None,
|
||||||
|
actor_organization: context.existing_org_id,
|
||||||
|
message: format!("Invitation from {} declined.", invite.org_name),
|
||||||
|
invited_org,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn assign_credit_line(
|
pub fn assign_credit_line(
|
||||||
&self,
|
&self,
|
||||||
context: OrgCreditLineContext,
|
context: OrgCreditLineContext,
|
||||||
@ -1017,6 +1195,8 @@ fn current_org_field_value(org: &HotOrgRecord, field: &str) -> Result<Value, Str
|
|||||||
.map_err(|error| format!("Failed to serialize org fleet: {}", error)),
|
.map_err(|error| format!("Failed to serialize org fleet: {}", error)),
|
||||||
"members" => serde_json::to_value(&org.members)
|
"members" => serde_json::to_value(&org.members)
|
||||||
.map_err(|error| format!("Failed to serialize org members: {}", error)),
|
.map_err(|error| format!("Failed to serialize org members: {}", error)),
|
||||||
|
"pending_invites" => serde_json::to_value(&org.pending_invites)
|
||||||
|
.map_err(|error| format!("Failed to serialize org invites: {}", error)),
|
||||||
_ => Err(format!("Unknown field: {}", field)),
|
_ => Err(format!("Unknown field: {}", field)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user