Skip to main content

alibi_plugins/organization/
native.rs

1//! Typed logical organization operations, independent of HTTP request construction.
2use super::{OrganizationPlugin, handlers, types};
3use crate::endpoint::{definition, error_response, validation};
4use alibi_core::endpoint::{
5    EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
6};
7use alibi_core::entity::{AuthOrganization, AuthUser};
8use alibi_core::session::SessionRequest;
9use alibi_core::types::{Team, TeamMember};
10use alibi_core::utils::json::JsValue;
11use alibi_core::wire::InvitationView;
12use alibi_core::{AuthContext, AuthError, AuthResponse, AuthResult, AuthSchema, HttpMethod};
13use std::collections::HashMap;
14
15pub(super) fn definitions() -> Vec<EndpointDefinition> {
16    vec![
17        definition(
18            "listOrganizations",
19            "listOrganizations",
20            Some("/organization/list"),
21            HttpMethod::Get,
22        ),
23        definition(
24            "updateOrganization",
25            "updateOrganization",
26            Some("/organization/update"),
27            HttpMethod::Post,
28        ),
29        definition(
30            "getOrganization",
31            "getOrganization",
32            Some("/organization/get-organization"),
33            HttpMethod::Get,
34        ),
35        definition(
36            "getFullOrganization",
37            "getFullOrganization",
38            Some("/organization/get-full-organization"),
39            HttpMethod::Get,
40        ),
41        definition(
42            "checkOrganizationSlug",
43            "checkOrganizationSlug",
44            Some("/organization/check-slug"),
45            HttpMethod::Post,
46        ),
47        definition(
48            "setActiveOrganization",
49            "setActiveOrganization",
50            Some("/organization/set-active"),
51            HttpMethod::Post,
52        ),
53        definition(
54            "leaveOrganization",
55            "leaveOrganization",
56            Some("/organization/leave"),
57            HttpMethod::Post,
58        ),
59        definition(
60            "inviteMember",
61            "inviteMember",
62            Some("/organization/invite-member"),
63            HttpMethod::Post,
64        ),
65        definition(
66            "getInvitation",
67            "getInvitation",
68            Some("/organization/get-invitation"),
69            HttpMethod::Get,
70        ),
71        definition(
72            "listInvitations",
73            "listInvitations",
74            Some("/organization/list-invitations"),
75            HttpMethod::Get,
76        ),
77        definition(
78            "listUserInvitations",
79            "listUserInvitations",
80            Some("/organization/list-user-invitations"),
81            HttpMethod::Get,
82        ),
83        definition(
84            "acceptInvitation",
85            "acceptInvitation",
86            Some("/organization/accept-invitation"),
87            HttpMethod::Post,
88        ),
89        definition(
90            "rejectInvitation",
91            "rejectInvitation",
92            Some("/organization/reject-invitation"),
93            HttpMethod::Post,
94        ),
95        definition(
96            "cancelInvitation",
97            "cancelInvitation",
98            Some("/organization/cancel-invitation"),
99            HttpMethod::Post,
100        ),
101        definition(
102            "getActiveMember",
103            "getActiveMember",
104            Some("/organization/get-active-member"),
105            HttpMethod::Get,
106        ),
107        definition(
108            "getActiveMemberRole",
109            "getActiveMemberRole",
110            Some("/organization/get-active-member-role"),
111            HttpMethod::Get,
112        ),
113        definition(
114            "listMembers",
115            "listMembers",
116            Some("/organization/list-members"),
117            HttpMethod::Get,
118        ),
119        definition(
120            "updateMemberRole",
121            "updateMemberRole",
122            Some("/organization/update-member-role"),
123            HttpMethod::Post,
124        ),
125        definition(
126            "hasPermission",
127            "hasPermission",
128            Some("/organization/has-permission"),
129            HttpMethod::Post,
130        ),
131        definition(
132            "createTeam",
133            "createTeam",
134            Some("/organization/create-team"),
135            HttpMethod::Post,
136        ),
137        definition(
138            "updateTeam",
139            "updateTeam",
140            Some("/organization/update-team"),
141            HttpMethod::Post,
142        ),
143        definition(
144            "removeTeam",
145            "removeTeam",
146            Some("/organization/remove-team"),
147            HttpMethod::Post,
148        ),
149        definition(
150            "setActiveTeam",
151            "setActiveTeam",
152            Some("/organization/set-active-team"),
153            HttpMethod::Post,
154        ),
155        definition(
156            "addTeamMember",
157            "addTeamMember",
158            Some("/organization/add-team-member"),
159            HttpMethod::Post,
160        ),
161        definition(
162            "removeTeamMember",
163            "removeTeamMember",
164            Some("/organization/remove-team-member"),
165            HttpMethod::Post,
166        ),
167        definition(
168            "listTeams",
169            "listTeams",
170            Some("/organization/list-teams"),
171            HttpMethod::Get,
172        ),
173        definition(
174            "listUserTeams",
175            "listUserTeams",
176            Some("/organization/list-user-teams"),
177            HttpMethod::Get,
178        ),
179        definition(
180            "listTeamMembers",
181            "listTeamMembers",
182            Some("/organization/list-team-members"),
183            HttpMethod::Get,
184        ),
185        definition(
186            "createOrganizationRole",
187            "createOrganizationRole",
188            Some("/organization/create-role"),
189            HttpMethod::Post,
190        ),
191        definition(
192            "updateOrganizationRole",
193            "updateOrganizationRole",
194            Some("/organization/update-role"),
195            HttpMethod::Post,
196        ),
197        definition(
198            "deleteOrganizationRole",
199            "deleteOrganizationRole",
200            Some("/organization/delete-role"),
201            HttpMethod::Post,
202        ),
203        definition(
204            "getOrganizationRole",
205            "getOrganizationRole",
206            Some("/organization/get-role"),
207            HttpMethod::Get,
208        ),
209        definition(
210            "listOrganizationRoles",
211            "listOrganizationRoles",
212            Some("/organization/list-roles"),
213            HttpMethod::Get,
214        ),
215    ]
216}
217
218pub(super) fn validate(call: &EndpointCall) -> AuthResult<EndpointInput> {
219    // Native calls validate logical values after before hooks, without media-type or URL decoding.
220    match call.operation_id() {
221        "updateOrganization" => {
222            _ = handlers::org_input::update_value(call.body()).map_err(error_response)?;
223        }
224        "getOrganization" => {
225            let _: types::GetOrganizationQuery = call
226                .query_as()
227                .map_err(|error| validation(error.to_string()))?;
228        }
229        "getFullOrganization" => {
230            let _: types::GetFullOrganizationQuery = call
231                .query_as()
232                .map_err(|error| validation(error.to_string()))?;
233        }
234        "checkOrganizationSlug" => {
235            let _: types::CheckSlugRequest = call
236                .body_as()
237                .map_err(|error| validation(error.to_string()))?;
238        }
239        "setActiveOrganization" => {
240            _ = handlers::org_input::set_active_value(call.body()).map_err(error_response)?;
241        }
242        "leaveOrganization" => {
243            let _: types::LeaveOrganizationRequest = call
244                .body_as()
245                .map_err(|error| validation(error.to_string()))?;
246        }
247        "inviteMember" => {
248            _ = handlers::org_input::invitation_create_value(call.body())
249                .map_err(error_response)?;
250        }
251        "getInvitation" => {
252            let _: types::GetInvitationQuery = call
253                .query_as()
254                .map_err(|error| validation(error.to_string()))?;
255        }
256        "listInvitations" => {
257            let _: types::ListInvitationsQuery = call
258                .query_as()
259                .map_err(|error| validation(error.to_string()))?;
260        }
261        "listUserInvitations" => {
262            let _: types::ListUserInvitationsQuery = call
263                .query_as()
264                .map_err(|error| validation(error.to_string()))?;
265        }
266        "acceptInvitation" => {
267            let _: types::AcceptInvitationRequest = call
268                .body_as()
269                .map_err(|error| validation(error.to_string()))?;
270        }
271        "rejectInvitation" => {
272            let _: types::RejectInvitationRequest = call
273                .body_as()
274                .map_err(|error| validation(error.to_string()))?;
275        }
276        "cancelInvitation" => {
277            let _: types::CancelInvitationRequest = call
278                .body_as()
279                .map_err(|error| validation(error.to_string()))?;
280        }
281        "getActiveMemberRole" => {
282            let _: types::GetActiveMemberRoleQuery = call
283                .query_as()
284                .map_err(|error| validation(error.to_string()))?;
285        }
286        "listMembers" => {
287            let _: types::ListMembersQuery = call
288                .query_as()
289                .map_err(|error| validation(error.to_string()))?;
290        }
291        "updateMemberRole" => {
292            _ = handlers::org_input::member_role_update_value(call.body())
293                .map_err(error_response)?;
294        }
295        "hasPermission" => {
296            let _: types::HasPermissionRequest = call
297                .body_as()
298                .map_err(|error| validation(error.to_string()))?;
299        }
300        "createTeam" => {
301            let _: types::CreateTeamRequest = call
302                .body_as()
303                .map_err(|error| validation(error.to_string()))?;
304        }
305        "updateTeam" => {
306            let _: types::UpdateTeamRequest = call
307                .body_as()
308                .map_err(|error| validation(error.to_string()))?;
309        }
310        "removeTeam" => {
311            let _: types::RemoveTeamRequest = call
312                .body_as()
313                .map_err(|error| validation(error.to_string()))?;
314        }
315        "setActiveTeam" => {
316            let _: types::SetActiveTeamRequest = call
317                .body_as()
318                .map_err(|error| validation(error.to_string()))?;
319        }
320        "addTeamMember" => {
321            let _: types::TeamMemberRequest = call
322                .body_as()
323                .map_err(|error| validation(error.to_string()))?;
324        }
325        "removeTeamMember" => {
326            let _: types::TeamMemberRequest = call
327                .body_as()
328                .map_err(|error| validation(error.to_string()))?;
329        }
330        "listTeams" => {
331            let _: types::TeamQuery = call
332                .query_as()
333                .map_err(|error| validation(error.to_string()))?;
334        }
335        "listUserTeams" => {
336            let _: types::TeamQuery = call
337                .query_as()
338                .map_err(|error| validation(error.to_string()))?;
339        }
340        "listTeamMembers" => {
341            let _: types::TeamQuery = call
342                .query_as()
343                .map_err(|error| validation(error.to_string()))?;
344        }
345        "createOrganizationRole" => {
346            let _: types::CreateRoleRequest = call
347                .body_as()
348                .map_err(|error| validation(error.to_string()))?;
349        }
350        "updateOrganizationRole" => {
351            let _: types::UpdateRoleRequest = call
352                .body_as()
353                .map_err(|error| validation(error.to_string()))?;
354        }
355        "deleteOrganizationRole" => {
356            let _: types::RoleQuery = call
357                .body_as()
358                .map_err(|error| validation(error.to_string()))?;
359        }
360        "getOrganizationRole" => {
361            let _: types::RoleQuery = call
362                .query_as()
363                .map_err(|error| validation(error.to_string()))?;
364        }
365        "listOrganizationRoles" => {
366            let _: types::RoleQuery = call
367                .query_as()
368                .map_err(|error| validation(error.to_string()))?;
369        }
370        _ => {}
371    }
372    Ok(EndpointInput {
373        body: call.body().cloned(),
374        query: call.query().cloned(),
375    })
376}
377
378impl OrganizationPlugin {
379    /// Construct a typed native call through installed dispatch hooks.
380    /// # Errors
381    /// Returns a serialization error for input that cannot be represented.
382    #[must_use]
383    pub fn list_organizations_endpoint() -> ServerEndpoint<Vec<types::OrganizationResponse>> {
384        ServerEndpoint::new("organization", "listOrganizations")
385    }
386    /// Construct a typed native call through installed dispatch hooks.
387    /// # Errors
388    /// Returns a serialization error for input that cannot be represented.
389    pub fn update_endpoint(
390        input: &types::UpdateOrganizationRequest,
391    ) -> AuthResult<ServerEndpoint<Option<types::CreatedOrganizationResponse>>> {
392        ServerEndpoint::new("organization", "updateOrganization").with_body(input)
393    }
394    /// Construct a typed native call through installed dispatch hooks.
395    /// # Errors
396    /// Returns a serialization error for input that cannot be represented.
397    pub fn get_organization_endpoint(
398        input: &types::GetOrganizationQuery,
399    ) -> AuthResult<ServerEndpoint<Option<types::OrganizationResponse>>> {
400        ServerEndpoint::new("organization", "getOrganization").with_query(input)
401    }
402    /// Construct a typed native call through installed dispatch hooks.
403    /// # Errors
404    /// Returns a serialization error for input that cannot be represented.
405    pub fn get_full_organization_endpoint(
406        input: &types::GetFullOrganizationQuery,
407    ) -> AuthResult<
408        ServerEndpoint<
409            Option<types::FullOrganizationResponse<types::OrganizationResponse, InvitationView>>,
410        >,
411    > {
412        ServerEndpoint::new("organization", "getFullOrganization").with_query(input)
413    }
414    /// Construct a typed native call through installed dispatch hooks.
415    /// # Errors
416    /// Returns a serialization error for input that cannot be represented.
417    pub fn check_slug_endpoint(
418        input: &types::CheckSlugRequest,
419    ) -> AuthResult<ServerEndpoint<types::CheckSlugResponse>> {
420        ServerEndpoint::new("organization", "checkOrganizationSlug").with_body(input)
421    }
422    /// Construct a typed native call through installed dispatch hooks.
423    /// # Errors
424    /// Returns a serialization error for input that cannot be represented.
425    pub fn set_active_endpoint(
426        input: &types::SetActiveOrganizationRequest,
427    ) -> AuthResult<ServerEndpoint<Option<types::OrganizationResponse>>> {
428        ServerEndpoint::new("organization", "setActiveOrganization").with_body(input)
429    }
430    /// Construct a typed native call through installed dispatch hooks.
431    /// # Errors
432    /// Returns a serialization error for input that cannot be represented.
433    pub fn leave_endpoint(
434        input: &types::LeaveOrganizationRequest,
435    ) -> AuthResult<ServerEndpoint<types::SuccessResponse>> {
436        ServerEndpoint::new("organization", "leaveOrganization").with_body(input)
437    }
438    /// Construct a typed native call through installed dispatch hooks.
439    /// # Errors
440    /// Returns a serialization error for input that cannot be represented.
441    pub fn invite_member_endpoint(
442        input: &types::InviteMemberRequest,
443    ) -> AuthResult<ServerEndpoint<InvitationView>> {
444        ServerEndpoint::new("organization", "inviteMember").with_body(input)
445    }
446    /// Construct a typed native call through installed dispatch hooks.
447    /// # Errors
448    /// Returns a serialization error for input that cannot be represented.
449    pub fn get_invitation_endpoint(
450        input: &types::GetInvitationQuery,
451    ) -> AuthResult<ServerEndpoint<types::GetInvitationResponse<InvitationView>>> {
452        ServerEndpoint::new("organization", "getInvitation").with_query(input)
453    }
454    /// Construct a typed native call through installed dispatch hooks.
455    /// # Errors
456    /// Returns a serialization error for input that cannot be represented.
457    pub fn list_invitations_endpoint(
458        input: &types::ListInvitationsQuery,
459    ) -> AuthResult<ServerEndpoint<Vec<InvitationView>>> {
460        ServerEndpoint::new("organization", "listInvitations").with_query(input)
461    }
462    /// Construct a typed native call through installed dispatch hooks.
463    /// # Errors
464    /// Returns a serialization error for input that cannot be represented.
465    pub fn list_user_invitations_endpoint(
466        input: &types::ListUserInvitationsQuery,
467    ) -> AuthResult<ServerEndpoint<Vec<types::UserInvitationResponse<InvitationView>>>> {
468        ServerEndpoint::new("organization", "listUserInvitations").with_query(input)
469    }
470    /// Construct a typed native call through installed dispatch hooks.
471    /// # Errors
472    /// Returns a serialization error for input that cannot be represented.
473    pub fn accept_invitation_endpoint(
474        input: &types::AcceptInvitationRequest,
475    ) -> AuthResult<
476        ServerEndpoint<types::AcceptInvitationResponse<InvitationView, types::BasicMemberResponse>>,
477    > {
478        ServerEndpoint::new("organization", "acceptInvitation").with_body(input)
479    }
480    /// Construct a typed native call through installed dispatch hooks.
481    /// # Errors
482    /// Returns a serialization error for input that cannot be represented.
483    pub fn reject_invitation_endpoint(
484        input: &types::RejectInvitationRequest,
485    ) -> AuthResult<
486        ServerEndpoint<
487            types::AcceptInvitationResponse<InvitationView, Option<types::BasicMemberResponse>>,
488        >,
489    > {
490        ServerEndpoint::new("organization", "rejectInvitation").with_body(input)
491    }
492    /// Construct a typed native call through installed dispatch hooks.
493    /// # Errors
494    /// Returns a serialization error for input that cannot be represented.
495    pub fn cancel_invitation_endpoint(
496        input: &types::CancelInvitationRequest,
497    ) -> AuthResult<ServerEndpoint<InvitationView>> {
498        ServerEndpoint::new("organization", "cancelInvitation").with_body(input)
499    }
500    /// Construct a typed native call through installed dispatch hooks.
501    /// # Errors
502    /// Returns a serialization error for input that cannot be represented.
503    #[must_use]
504    pub fn get_active_member_endpoint() -> ServerEndpoint<types::MemberResponse> {
505        ServerEndpoint::new("organization", "getActiveMember")
506    }
507    /// Construct a typed native call through installed dispatch hooks.
508    /// # Errors
509    /// Returns a serialization error for input that cannot be represented.
510    pub fn get_active_member_role_endpoint(
511        input: &types::GetActiveMemberRoleQuery,
512    ) -> AuthResult<ServerEndpoint<types::GetActiveMemberRoleResponse>> {
513        ServerEndpoint::new("organization", "getActiveMemberRole").with_query(input)
514    }
515    /// Construct a typed native call through installed dispatch hooks.
516    /// # Errors
517    /// Returns a serialization error for input that cannot be represented.
518    pub fn list_members_endpoint(
519        input: &types::ListMembersQuery,
520    ) -> AuthResult<ServerEndpoint<types::ListMembersResponse>> {
521        ServerEndpoint::new("organization", "listMembers").with_query(input)
522    }
523    /// Construct a typed native call through installed dispatch hooks.
524    /// # Errors
525    /// Returns a serialization error for input that cannot be represented.
526    pub fn update_member_role_endpoint(
527        input: &types::UpdateMemberRoleRequest,
528    ) -> AuthResult<ServerEndpoint<types::BasicMemberResponse>> {
529        ServerEndpoint::new("organization", "updateMemberRole").with_body(input)
530    }
531    /// Construct a typed native call through installed dispatch hooks.
532    /// # Errors
533    /// Returns a serialization error for input that cannot be represented.
534    pub fn has_permission_endpoint(
535        input: &types::HasPermissionRequest,
536    ) -> AuthResult<ServerEndpoint<types::HasPermissionResponse>> {
537        ServerEndpoint::new("organization", "hasPermission").with_body(input)
538    }
539    /// Construct a typed native call through installed dispatch hooks.
540    /// # Errors
541    /// Returns a serialization error for input that cannot be represented.
542    pub fn create_team_endpoint(
543        input: &types::CreateTeamRequest,
544    ) -> AuthResult<ServerEndpoint<Team>> {
545        ServerEndpoint::new("organization", "createTeam").with_body(input)
546    }
547    /// Construct a typed native call through installed dispatch hooks.
548    /// # Errors
549    /// Returns a serialization error for input that cannot be represented.
550    pub fn update_team_endpoint(
551        input: &types::UpdateTeamRequest,
552    ) -> AuthResult<ServerEndpoint<Team>> {
553        ServerEndpoint::new("organization", "updateTeam").with_body(input)
554    }
555    /// Construct a typed native call through installed dispatch hooks.
556    /// # Errors
557    /// Returns a serialization error for input that cannot be represented.
558    pub fn remove_team_endpoint(
559        input: &types::RemoveTeamRequest,
560    ) -> AuthResult<ServerEndpoint<types::MessageResponse>> {
561        ServerEndpoint::new("organization", "removeTeam").with_body(input)
562    }
563    /// Construct a typed native call through installed dispatch hooks.
564    /// # Errors
565    /// Returns a serialization error for input that cannot be represented.
566    pub fn set_active_team_endpoint(
567        input: &types::SetActiveTeamRequest,
568    ) -> AuthResult<ServerEndpoint<Option<Team>>> {
569        ServerEndpoint::new("organization", "setActiveTeam").with_body(input)
570    }
571    /// Construct a typed native call through installed dispatch hooks.
572    /// # Errors
573    /// Returns a serialization error for input that cannot be represented.
574    pub fn add_team_member_endpoint(
575        input: &types::TeamMemberRequest,
576    ) -> AuthResult<ServerEndpoint<TeamMember>> {
577        ServerEndpoint::new("organization", "addTeamMember").with_body(input)
578    }
579    /// Construct a typed native call through installed dispatch hooks.
580    /// # Errors
581    /// Returns a serialization error for input that cannot be represented.
582    pub fn remove_team_member_endpoint(
583        input: &types::TeamMemberRequest,
584    ) -> AuthResult<ServerEndpoint<types::MessageResponse>> {
585        ServerEndpoint::new("organization", "removeTeamMember").with_body(input)
586    }
587    /// Construct a typed native call through installed dispatch hooks.
588    /// # Errors
589    /// Returns a serialization error for input that cannot be represented.
590    pub fn list_teams_endpoint(input: &types::TeamQuery) -> AuthResult<ServerEndpoint<Vec<Team>>> {
591        ServerEndpoint::new("organization", "listTeams").with_query(input)
592    }
593    /// Construct a typed native call through installed dispatch hooks.
594    /// # Errors
595    /// Returns a serialization error for input that cannot be represented.
596    pub fn list_user_teams_endpoint(
597        input: &types::TeamQuery,
598    ) -> AuthResult<ServerEndpoint<Vec<Team>>> {
599        ServerEndpoint::new("organization", "listUserTeams").with_query(input)
600    }
601    /// Construct a typed native call through installed dispatch hooks.
602    /// # Errors
603    /// Returns a serialization error for input that cannot be represented.
604    pub fn list_team_members_endpoint(
605        input: &types::TeamQuery,
606    ) -> AuthResult<ServerEndpoint<Vec<TeamMember>>> {
607        ServerEndpoint::new("organization", "listTeamMembers").with_query(input)
608    }
609    /// Construct a typed native call through installed dispatch hooks.
610    /// # Errors
611    /// Returns a serialization error for input that cannot be represented.
612    pub fn create_role_endpoint(
613        input: &types::CreateRoleRequest,
614    ) -> AuthResult<ServerEndpoint<types::CreateRoleResponse>> {
615        ServerEndpoint::new("organization", "createOrganizationRole").with_body(input)
616    }
617    /// Construct a typed native call through installed dispatch hooks.
618    /// # Errors
619    /// Returns a serialization error for input that cannot be represented.
620    pub fn update_role_endpoint(
621        input: &types::UpdateRoleRequest,
622    ) -> AuthResult<ServerEndpoint<types::UpdateRoleResponse>> {
623        ServerEndpoint::new("organization", "updateOrganizationRole").with_body(input)
624    }
625    /// Construct a typed native call through installed dispatch hooks.
626    /// # Errors
627    /// Returns a serialization error for input that cannot be represented.
628    pub fn delete_role_endpoint(
629        input: &types::RoleQuery,
630    ) -> AuthResult<ServerEndpoint<types::SuccessResponse>> {
631        ServerEndpoint::new("organization", "deleteOrganizationRole").with_body(input)
632    }
633    /// Construct a typed native call through installed dispatch hooks.
634    /// # Errors
635    /// Returns a serialization error for input that cannot be represented.
636    pub fn get_role_endpoint(
637        input: &types::RoleQuery,
638    ) -> AuthResult<ServerEndpoint<types::RoleResponse>> {
639        ServerEndpoint::new("organization", "getOrganizationRole").with_query(input)
640    }
641    /// Construct a typed native call through installed dispatch hooks.
642    /// # Errors
643    /// Returns a serialization error for input that cannot be represented.
644    pub fn list_roles_endpoint(
645        input: &types::RoleQuery,
646    ) -> AuthResult<ServerEndpoint<Vec<types::RoleResponse>>> {
647        ServerEndpoint::new("organization", "listOrganizationRoles").with_query(input)
648    }
649}
650
651fn response(output: AuthResponse) -> AuthResult<EndpointResponse> {
652    let value = if output.body.is_empty() {
653        JsValue::Null
654    } else {
655        alibi_core::utils::json::from_slice::<JsValue>(&output.body)?
656    };
657    let mut response = if output.status >= 400 {
658        EndpointResponse::error(error_response(output.clone())).with_error_body(value)
659    } else {
660        EndpointResponse::value(value)
661    };
662    response.set_status(Some(output.status));
663    response.merge_headers(output.headers);
664    Ok(response)
665}
666
667fn query_pairs(call: &EndpointCall) -> AuthResult<HashMap<String, String>> {
668    let Some(fields) = call.query().and_then(JsValue::as_object) else {
669        return Ok(HashMap::new());
670    };
671    fields
672        .iter()
673        .map(|(key, value)| {
674            value
675                .coerce_string()
676                .map(|value| (key.clone(), value))
677                .map_err(validation)
678        })
679        .collect()
680}
681
682pub(super) async fn execute<S: AuthSchema>(
683    plugin: &OrganizationPlugin,
684    call: &EndpointCall,
685    ctx: &AuthContext<S>,
686) -> AuthResult<EndpointResponse> {
687    let config = &plugin.config;
688    if call.operation_id() == "listUserInvitations" {
689        let query: types::ListUserInvitationsQuery = call.query_as()?;
690        if let Some(email) = query.email.as_deref().filter(|email| !email.is_empty()) {
691            if call.request().is_some() {
692                return Err(AuthError::bad_request(
693                    "User email cannot be passed for client side API calls.",
694                ));
695            }
696            return EndpointResponse::json(
697                &handlers::invitation::list_user_invitations_for_email_core(email, ctx).await?,
698            );
699        }
700    }
701    let (user, session) = ctx.require_cached_session(call).await.map_err(|error| {
702        if matches!(
703            error,
704            AuthError::Unauthenticated | AuthError::SessionNotFound
705        ) {
706            handlers::extension_common::org_error(401, "UNAUTHORIZED")
707        } else {
708            error
709        }
710    })?;
711    match call.operation_id() {
712        "listOrganizations" => {
713            EndpointResponse::json(&handlers::org::list_organizations_core(&user, ctx).await?)
714        }
715        "updateOrganization" => {
716            let (body, metadata) =
717                handlers::org_input::update_value(call.body()).map_err(error_response)?;
718            EndpointResponse::json(
719                &handlers::org::update_organization_core(
720                    &body, metadata, &user, &session, config, ctx,
721                )
722                .await?,
723            )
724        }
725        "getOrganization" => {
726            let query: types::GetOrganizationQuery = call.query_as()?;
727            let organization =
728                if let Some(slug) = query.organization_slug.as_deref().filter(|s| !s.is_empty()) {
729                    ctx.database.get_organization_by_slug(slug).await?
730                } else if let Some(id) = query
731                    .organization_id
732                    .as_deref()
733                    .filter(|s| !s.is_empty())
734                    .or(session.active_organization_id.as_deref())
735                {
736                    ctx.database.get_organization_by_id(id).await?
737                } else {
738                    return EndpointResponse::json(&Option::<types::OrganizationResponse>::None);
739                }
740                .ok_or_else(|| AuthError::bad_request("Organization not found"))?;
741            if ctx
742                .database
743                .get_member(&organization.id(), &user.id())
744                .await?
745                .is_none()
746            {
747                _ = ctx
748                    .database
749                    .update_session_active_organization_record(&session.token, None)
750                    .await?;
751                return Err(AuthError::forbidden(
752                    "User is not a member of the organization",
753                ));
754            }
755            EndpointResponse::json(&types::OrganizationResponse::from_stored_organization(
756                &organization,
757            )?)
758        }
759        "getFullOrganization" => EndpointResponse::json(
760            &handlers::org::get_full_organization_core(
761                &call.query_as()?,
762                &user,
763                &session,
764                config,
765                ctx,
766            )
767            .await
768            .map_err(AuthError::from)?,
769        ),
770        "checkOrganizationSlug" => {
771            EndpointResponse::json(&handlers::org::check_slug_core(&call.body_as()?, ctx).await?)
772        }
773        "setActiveOrganization" => {
774            let body =
775                handlers::org_input::set_active_value(call.body()).map_err(error_response)?;
776            let organization =
777                handlers::org::set_active_organization_core(&body, &user, &session, ctx).await?;
778            if organization.is_some()
779                || session
780                    .active_organization_id
781                    .as_deref()
782                    .is_some_and(|id| !id.is_empty())
783            {
784                use alibi_core::utils::cookie_utils::{
785                    create_session_cookie_with_max_age, create_session_like_cookie,
786                    related_cookie_name, sign_cookie_value, verify_cookie_value,
787                };
788                let preference = related_cookie_name(&ctx.config, "dont_remember");
789                let dont_remember = call.session_headers().get("cookie").is_some_and(|header| {
790                    cookie::Cookie::split_parse(header)
791                        .flatten()
792                        .find(|cookie| cookie.name() == preference)
793                        .and_then(|cookie| {
794                            verify_cookie_value(cookie.value(), ctx.config.current_secret())
795                        })
796                        .is_some_and(|value| !value.is_empty())
797                });
798                call.queue_response_header(
799                    "set-cookie",
800                    create_session_cookie_with_max_age(
801                        Some(&session.token),
802                        (!dont_remember).then(|| ctx.config.session.expires_in.num_seconds()),
803                        &ctx.config,
804                    )?,
805                );
806                if dont_remember {
807                    call.queue_response_header(
808                        "set-cookie",
809                        create_session_like_cookie(
810                            &preference,
811                            &sign_cookie_value("true", ctx.config.current_secret()),
812                            None,
813                            &ctx.config,
814                        )?,
815                    );
816                }
817            }
818            EndpointResponse::json(&organization)
819        }
820        "leaveOrganization" => EndpointResponse::json(
821            &handlers::org::leave_organization_core(&call.body_as()?, &user, &session, config, ctx)
822                .await?,
823        ),
824        "inviteMember" => {
825            let body = handlers::org_input::invitation_create_value(call.body())
826                .map_err(error_response)?;
827            EndpointResponse::json(
828                &handlers::invitation::invite_member_core(&body, &user, &session, config, ctx)
829                    .await?,
830            )
831        }
832        "getInvitation" => EndpointResponse::json(
833            &handlers::invitation::get_invitation_core(&call.query_as()?, &user, config, ctx)
834                .await?,
835        ),
836        "listInvitations" => EndpointResponse::json(
837            &handlers::invitation::list_invitations_core(&call.query_as()?, &user, &session, ctx)
838                .await?,
839        ),
840        "listUserInvitations" => EndpointResponse::json(
841            &handlers::invitation::list_user_invitations_core(&user, ctx).await?,
842        ),
843        "acceptInvitation" => {
844            let transport = handlers::invitation_acceptance::AcceptanceTransport::native(call);
845            let result = handlers::invitation_acceptance::accept(
846                &call.body_as()?,
847                &user,
848                &session,
849                config,
850                ctx,
851                &transport,
852            )
853            .await;
854            if matches!(result, Err(AuthError::Internal(_) | AuthError::Database(_))) {
855                transport.discard_cookies();
856            }
857            EndpointResponse::json(&result?)
858        }
859        "rejectInvitation" => EndpointResponse::json(
860            &handlers::invitation::reject_invitation_core(&call.body_as()?, &user, config, ctx)
861                .await?,
862        ),
863        "cancelInvitation" => EndpointResponse::json(
864            &handlers::invitation::cancel_invitation_core(&call.body_as()?, &user, config, ctx)
865                .await?,
866        ),
867        "getActiveMember" => EndpointResponse::json(
868            &handlers::member::get_active_member_core(&user, &session, ctx).await?,
869        ),
870        "getActiveMemberRole" => EndpointResponse::json(
871            &handlers::member::get_active_member_role_core(&call.query_as()?, &user, &session, ctx)
872                .await?,
873        ),
874        "listMembers" => EndpointResponse::json(
875            &handlers::member::list_members_core(&call.query_as()?, &user, &session, config, ctx)
876                .await
877                .map_err(AuthError::from)?,
878        ),
879        "updateMemberRole" => {
880            let body = handlers::org_input::member_role_update_value(call.body())
881                .map_err(error_response)?;
882            response(
883                handlers::member::update_member_role_response(&body, &user, &session, config, ctx)
884                    .await?,
885            )
886        }
887        "hasPermission" => EndpointResponse::json(
888            &handlers::has_permission_core(&call.body_as()?, &user, &session, config, ctx).await?,
889        ),
890        "createTeam" | "updateTeam" | "removeTeam" | "setActiveTeam" | "addTeamMember"
891        | "removeTeamMember" | "listTeams" | "listUserTeams" | "listTeamMembers" => {
892            let path = match call.operation_id() {
893                "createTeam" => "/organization/create-team",
894                "updateTeam" => "/organization/update-team",
895                "removeTeam" => "/organization/remove-team",
896                "setActiveTeam" => "/organization/set-active-team",
897                "addTeamMember" => "/organization/add-team-member",
898                "removeTeamMember" => "/organization/remove-team-member",
899                "listTeams" => "/organization/list-teams",
900                "listUserTeams" => "/organization/list-user-teams",
901                "listTeamMembers" => "/organization/list-team-members",
902                _ => return Err(AuthError::not_found("Unknown operation")),
903            };
904            let method = call.default_method();
905            let query = query_pairs(call)?;
906            let body = call
907                .body()
908                .map(JsValue::to_json_value)
909                .transpose()
910                .map_err(|error| validation(error.to_string()))?;
911            let result = handlers::team::team_core(
912                method,
913                path,
914                body,
915                &query,
916                user,
917                session,
918                call.request(),
919                false,
920                ctx,
921                config,
922            )
923            .await?;
924            response(result.ok_or_else(|| AuthError::not_found("Unknown operation"))?)
925        }
926        "createOrganizationRole"
927        | "updateOrganizationRole"
928        | "deleteOrganizationRole"
929        | "getOrganizationRole"
930        | "listOrganizationRoles" => {
931            let path = match call.operation_id() {
932                "createOrganizationRole" => "/organization/create-role",
933                "updateOrganizationRole" => "/organization/update-role",
934                "deleteOrganizationRole" => "/organization/delete-role",
935                "getOrganizationRole" => "/organization/get-role",
936                "listOrganizationRoles" => "/organization/list-roles",
937                _ => return Err(AuthError::not_found("Unknown operation")),
938            };
939            let method = call.default_method();
940            let query = query_pairs(call)?;
941            let body = call
942                .body()
943                .map(JsValue::to_json_value)
944                .transpose()
945                .map_err(|error| validation(error.to_string()))?
946                .and_then(|value| value.as_object().cloned())
947                .unwrap_or_default();
948            let result =
949                handlers::role::role_core(method, path, body, &query, call, ctx, config).await?;
950            response(result.ok_or_else(|| AuthError::not_found("Unknown operation"))?)
951        }
952        _ => Err(AuthError::not_found("Unregistered organization operation")),
953    }
954}