diff --git a/crates/handlers/src/admin/model.rs b/crates/handlers/src/admin/model.rs index 497881596..3863ca84f 100644 --- a/crates/handlers/src/admin/model.rs +++ b/crates/handlers/src/admin/model.rs @@ -30,6 +30,42 @@ pub trait Resource { } } +/// A user's email address +#[derive(Serialize, JsonSchema)] +pub struct UserEmail { + #[serde(skip)] + id: Ulid, + + /// The email address + email: String, + + /// When the email address was created/verified + created_at: DateTime, +} + +impl UserEmail { + /// Samples of user emails + pub fn samples() -> [Vec; 1] { + [ + Vec::from(Self { + id: Ulid::from_bytes([0x01; 16]), + created_at: DateTime::default(), + email: "alice@example.com".to_owned(), + }), + ] + } +} + +impl From for UserEmail { + fn from(email: mas_data_model::UserEmail) -> Self { + Self { + id: email.id, + email: email.email, + created_at: email.created_at, + } + } +} + /// A user #[derive(Serialize, JsonSchema)] pub struct User { diff --git a/crates/handlers/src/admin/v1/mod.rs b/crates/handlers/src/admin/v1/mod.rs index 73060f825..b30bb91aa 100644 --- a/crates/handlers/src/admin/v1/mod.rs +++ b/crates/handlers/src/admin/v1/mod.rs @@ -44,6 +44,10 @@ where "/users/{id}", get_with(self::users::get, self::users::get_doc), ) + .api_route( + "/users/{id}/emails", + get_with(self::users::get_emails, self::users::get_emails_doc), + ) .api_route( "/users/{id}/set-password", post_with(self::users::set_password, self::users::set_password_doc), diff --git a/crates/handlers/src/admin/v1/users/emails.rs b/crates/handlers/src/admin/v1/users/emails.rs new file mode 100644 index 000000000..051a6ba86 --- /dev/null +++ b/crates/handlers/src/admin/v1/users/emails.rs @@ -0,0 +1,83 @@ +// Copyright 2024 New Vector Ltd. +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +use aide::{transform::TransformOperation, OperationIo}; +use axum::{response::IntoResponse, Json}; +use hyper::StatusCode; +use ulid::Ulid; +use mas_storage::{Pagination, RepositoryAccess}; +use mas_storage::user::{UserEmailFilter, UserEmailRepository}; +use crate::{ + admin::{ + call_context::CallContext, + model::User, + params::UlidPathParam, + response::{ErrorResponse, SingleResponse}, + }, + impl_from_error_for_route, +}; +use crate::admin::model::UserEmail; + +#[derive(Debug, thiserror::Error, OperationIo)] +#[aide(output_with = "Json")] +pub enum RouteError { + #[error(transparent)] + Internal(Box), + + #[error("User ID {0} not found")] + NotFound(Ulid), +} + +impl_from_error_for_route!(mas_storage::RepositoryError); + +impl IntoResponse for RouteError { + fn into_response(self) -> axum::response::Response { + let error = ErrorResponse::from_error(&self); + let status = match self { + Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::NotFound(_) => StatusCode::NOT_FOUND, + }; + (status, Json(error)).into_response() + } +} + +pub fn doc(operation: TransformOperation) -> TransformOperation { + operation + .id("getUserEmails") + .summary("Get a user's emails") + .tag("user") + .response_with::<200, Json>>, _>(|t| { + let [sample, ..] = UserEmail::samples(); + let response = SingleResponse::new_canonical(sample); + t.description("User was found").example(response) + }) + .response_with::<404, RouteError, _>(|t| { + let response = ErrorResponse::from_error(&RouteError::NotFound(Ulid::nil())); + t.description("User was not found").example(response) + }) +} + +#[tracing::instrument(name = "handler.admin.v1.users.get_emails", skip_all, err)] +pub async fn handler( + CallContext { mut repo, .. }: CallContext, + id: UlidPathParam, +) -> Result>>, RouteError> { + let user = repo + .user() + .lookup(*id) + .await? + .ok_or(RouteError::NotFound(*id))?; + + let emails: Vec = repo + .user_email() + .all(&user) + .await? + .iter() + .map(|e| UserEmail::from(e)) + .into(); + + Ok(Json(SingleResponse::new_canonical(emails))) +} diff --git a/crates/handlers/src/admin/v1/users/get.rs b/crates/handlers/src/admin/v1/users/get.rs index 0f3be0867..46bbedd0f 100644 --- a/crates/handlers/src/admin/v1/users/get.rs +++ b/crates/handlers/src/admin/v1/users/get.rs @@ -8,7 +8,8 @@ use aide::{transform::TransformOperation, OperationIo}; use axum::{response::IntoResponse, Json}; use hyper::StatusCode; use ulid::Ulid; - +use mas_storage::{RepositoryAccess}; +use mas_storage::user::{UserEmailRepository}; use crate::{ admin::{ call_context::CallContext, diff --git a/crates/handlers/src/admin/v1/users/mod.rs b/crates/handlers/src/admin/v1/users/mod.rs index ff610f167..3c448b330 100644 --- a/crates/handlers/src/admin/v1/users/mod.rs +++ b/crates/handlers/src/admin/v1/users/mod.rs @@ -7,6 +7,7 @@ mod add; mod by_username; mod deactivate; +mod emails; mod get; mod list; mod lock; @@ -18,6 +19,7 @@ pub use self::{ add::{doc as add_doc, handler as add}, by_username::{doc as by_username_doc, handler as by_username}, deactivate::{doc as deactivate_doc, handler as deactivate}, + emails::{doc as get_emails_doc, handler as get_emails}, get::{doc as get_doc, handler as get}, list::{doc as list_doc, handler as list}, lock::{doc as lock_doc, handler as lock},