Skip to content

Commit a8940f0

Browse files
committed
Copy/paste attempt to make a "get user emails" API
1 parent b5ec07d commit a8940f0

File tree

5 files changed

+127
-1
lines changed

5 files changed

+127
-1
lines changed

crates/handlers/src/admin/model.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,42 @@ pub trait Resource {
3030
}
3131
}
3232

33+
/// A user's email address
34+
#[derive(Serialize, JsonSchema)]
35+
pub struct UserEmail {
36+
#[serde(skip)]
37+
id: Ulid,
38+
39+
/// The email address
40+
email: String,
41+
42+
/// When the email address was created/verified
43+
created_at: DateTime<Utc>,
44+
}
45+
46+
impl UserEmail {
47+
/// Samples of user emails
48+
pub fn samples() -> [Vec<Self>; 1] {
49+
[
50+
Vec::from(Self {
51+
id: Ulid::from_bytes([0x01; 16]),
52+
created_at: DateTime::default(),
53+
email: "[email protected]".to_owned(),
54+
}),
55+
]
56+
}
57+
}
58+
59+
impl From<mas_data_model::UserEmail> for UserEmail {
60+
fn from(email: mas_data_model::UserEmail) -> Self {
61+
Self {
62+
id: email.id,
63+
email: email.email,
64+
created_at: email.created_at,
65+
}
66+
}
67+
}
68+
3369
/// A user
3470
#[derive(Serialize, JsonSchema)]
3571
pub struct User {

crates/handlers/src/admin/v1/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ where
4444
"/users/{id}",
4545
get_with(self::users::get, self::users::get_doc),
4646
)
47+
.api_route(
48+
"/users/{id}/emails",
49+
get_with(self::users::get_emails, self::users::get_emails_doc),
50+
)
4751
.api_route(
4852
"/users/{id}/set-password",
4953
post_with(self::users::set_password, self::users::set_password_doc),
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2024 New Vector Ltd.
2+
// Copyright 2024 The Matrix.org Foundation C.I.C.
3+
//
4+
// SPDX-License-Identifier: AGPL-3.0-only
5+
// Please see LICENSE in the repository root for full details.
6+
7+
use aide::{transform::TransformOperation, OperationIo};
8+
use axum::{response::IntoResponse, Json};
9+
use hyper::StatusCode;
10+
use ulid::Ulid;
11+
use mas_storage::{Pagination, RepositoryAccess};
12+
use mas_storage::user::{UserEmailFilter, UserEmailRepository};
13+
use crate::{
14+
admin::{
15+
call_context::CallContext,
16+
model::User,
17+
params::UlidPathParam,
18+
response::{ErrorResponse, SingleResponse},
19+
},
20+
impl_from_error_for_route,
21+
};
22+
use crate::admin::model::UserEmail;
23+
24+
#[derive(Debug, thiserror::Error, OperationIo)]
25+
#[aide(output_with = "Json<ErrorResponse>")]
26+
pub enum RouteError {
27+
#[error(transparent)]
28+
Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
29+
30+
#[error("User ID {0} not found")]
31+
NotFound(Ulid),
32+
}
33+
34+
impl_from_error_for_route!(mas_storage::RepositoryError);
35+
36+
impl IntoResponse for RouteError {
37+
fn into_response(self) -> axum::response::Response {
38+
let error = ErrorResponse::from_error(&self);
39+
let status = match self {
40+
Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
41+
Self::NotFound(_) => StatusCode::NOT_FOUND,
42+
};
43+
(status, Json(error)).into_response()
44+
}
45+
}
46+
47+
pub fn doc(operation: TransformOperation) -> TransformOperation {
48+
operation
49+
.id("getUserEmails")
50+
.summary("Get a user's emails")
51+
.tag("user")
52+
.response_with::<200, Json<SingleResponse<Vec<UserEmail>>>, _>(|t| {
53+
let [sample, ..] = UserEmail::samples();
54+
let response = SingleResponse::new_canonical(sample);
55+
t.description("User was found").example(response)
56+
})
57+
.response_with::<404, RouteError, _>(|t| {
58+
let response = ErrorResponse::from_error(&RouteError::NotFound(Ulid::nil()));
59+
t.description("User was not found").example(response)
60+
})
61+
}
62+
63+
#[tracing::instrument(name = "handler.admin.v1.users.get_emails", skip_all, err)]
64+
pub async fn handler(
65+
CallContext { mut repo, .. }: CallContext,
66+
id: UlidPathParam,
67+
) -> Result<Json<SingleResponse<Vec<UserEmail>>>, RouteError> {
68+
let user = repo
69+
.user()
70+
.lookup(*id)
71+
.await?
72+
.ok_or(RouteError::NotFound(*id))?;
73+
74+
let emails: Vec<UserEmail> = repo
75+
.user_email()
76+
.all(&user)
77+
.await?
78+
.iter()
79+
.map(|e| UserEmail::from(e))
80+
.into();
81+
82+
Ok(Json(SingleResponse::new_canonical(emails)))
83+
}

crates/handlers/src/admin/v1/users/get.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use aide::{transform::TransformOperation, OperationIo};
88
use axum::{response::IntoResponse, Json};
99
use hyper::StatusCode;
1010
use ulid::Ulid;
11-
11+
use mas_storage::{RepositoryAccess};
12+
use mas_storage::user::{UserEmailRepository};
1213
use crate::{
1314
admin::{
1415
call_context::CallContext,

crates/handlers/src/admin/v1/users/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
mod add;
88
mod by_username;
99
mod deactivate;
10+
mod emails;
1011
mod get;
1112
mod list;
1213
mod lock;
@@ -18,6 +19,7 @@ pub use self::{
1819
add::{doc as add_doc, handler as add},
1920
by_username::{doc as by_username_doc, handler as by_username},
2021
deactivate::{doc as deactivate_doc, handler as deactivate},
22+
emails::{doc as get_emails_doc, handler as get_emails},
2123
get::{doc as get_doc, handler as get},
2224
list::{doc as list_doc, handler as list},
2325
lock::{doc as lock_doc, handler as lock},

0 commit comments

Comments
 (0)