mas_handlers/graphql/query/
user.rs1use async_graphql::{
8 Context, Enum, ID, Object,
9 connection::{Connection, Edge, OpaqueCursor, query},
10};
11use mas_storage::{Pagination, user::UserFilter};
12
13use crate::graphql::{
14 UserId,
15 model::{Cursor, NodeCursor, NodeType, PreloadedTotalCount, User},
16 state::ContextExt as _,
17};
18
19#[derive(Default)]
20pub struct UserQuery;
21
22#[Object]
23impl UserQuery {
24 pub async fn user(
26 &self,
27 ctx: &Context<'_>,
28 id: ID,
29 ) -> Result<Option<User>, async_graphql::Error> {
30 let id = NodeType::User.extract_ulid(&id)?;
31
32 let requester = ctx.requester();
33 if !requester.is_owner_or_admin(&UserId(id)) {
34 return Ok(None);
35 }
36
37 let state = ctx.state();
41 let mut repo = state.repository().await?;
42 let user = repo.user().lookup(id).await?;
43 repo.cancel().await?;
44
45 Ok(user.map(User))
46 }
47
48 async fn user_by_username(
50 &self,
51 ctx: &Context<'_>,
52 username: String,
53 ) -> Result<Option<User>, async_graphql::Error> {
54 let requester = ctx.requester();
55 let state = ctx.state();
56 let mut repo = state.repository().await?;
57
58 let user = repo.user().find_by_username(&username).await?;
59 let Some(user) = user else {
60 return Ok(None);
62 };
63
64 if !requester.is_owner_or_admin(&user) {
66 return Ok(None);
67 }
68
69 Ok(Some(User(user)))
70 }
71
72 async fn users(
76 &self,
77 ctx: &Context<'_>,
78
79 #[graphql(name = "state", desc = "List only users with the given state.")]
80 state_param: Option<UserState>,
81
82 #[graphql(
83 name = "canRequestAdmin",
84 desc = "List only users with the given 'canRequestAdmin' value"
85 )]
86 can_request_admin_param: Option<bool>,
87
88 #[graphql(desc = "Returns the elements in the list that come after the cursor.")]
89 after: Option<String>,
90 #[graphql(desc = "Returns the elements in the list that come before the cursor.")]
91 before: Option<String>,
92 #[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
93 #[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
94 ) -> Result<Connection<Cursor, User, PreloadedTotalCount>, async_graphql::Error> {
95 let requester = ctx.requester();
96 if !requester.is_admin() {
97 return Err(async_graphql::Error::new("Unauthorized"));
98 }
99
100 let state = ctx.state();
101 let mut repo = state.repository().await?;
102
103 query(
104 after,
105 before,
106 first,
107 last,
108 async |after, before, first, last| {
109 let after_id = after
110 .map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::User))
111 .transpose()?;
112 let before_id = before
113 .map(|x: OpaqueCursor<NodeCursor>| x.extract_for_type(NodeType::User))
114 .transpose()?;
115 let pagination = Pagination::try_new(before_id, after_id, first, last)?;
116
117 let filter = UserFilter::new();
119 let filter = match can_request_admin_param {
120 Some(true) => filter.can_request_admin_only(),
121 Some(false) => filter.cannot_request_admin_only(),
122 None => filter,
123 };
124 let filter = match state_param {
125 Some(UserState::Active) => filter.active_only(),
126 Some(UserState::Locked) => filter.locked_only(),
127 None => filter,
128 };
129
130 let page = repo.user().list(filter, pagination).await?;
131
132 let count = if ctx.look_ahead().field("totalCount").exists() {
134 Some(repo.user().count(filter).await?)
135 } else {
136 None
137 };
138
139 repo.cancel().await?;
140
141 let mut connection = Connection::with_additional_fields(
142 page.has_previous_page,
143 page.has_next_page,
144 PreloadedTotalCount(count),
145 );
146 connection.edges.extend(
147 page.edges.into_iter().map(|p| {
148 Edge::new(OpaqueCursor(NodeCursor(NodeType::User, p.id)), User(p))
149 }),
150 );
151
152 Ok::<_, async_graphql::Error>(connection)
153 },
154 )
155 .await
156 }
157}
158
159#[derive(Enum, Copy, Clone, Eq, PartialEq)]
161enum UserState {
162 Active,
164
165 Locked,
167}