Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit cc10495

Browse files
committed
Merge branches 'quenting/admin-api/doc' and 'quenting/admin-api/create-user' into quenting/admin-api/temp-merge-base
3 parents 5edeb59 + 57504d4 + 6784152 commit cc10495

File tree

26 files changed

+3592
-426
lines changed

26 files changed

+3592
-426
lines changed

crates/handlers/src/admin/mod.rs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,59 @@
1414

1515
use aide::{
1616
axum::ApiRouter,
17-
openapi::{OAuth2Flow, OAuth2Flows, OpenApi, SecurityScheme, Server, ServerVariable},
17+
openapi::{OAuth2Flow, OAuth2Flows, OpenApi, SecurityScheme, Server, Tag},
18+
};
19+
use axum::{
20+
extract::{FromRef, FromRequestParts, State},
21+
http::HeaderName,
22+
response::Html,
23+
Json, Router,
1824
};
19-
use axum::{extract::FromRequestParts, Json, Router};
2025
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
2126
use indexmap::IndexMap;
27+
use mas_axum_utils::FancyError;
2228
use mas_http::CorsLayerExt;
23-
use mas_router::{OAuth2AuthorizationEndpoint, OAuth2TokenEndpoint, SimpleRoute};
29+
use mas_matrix::BoxHomeserverConnection;
30+
use mas_router::{
31+
ApiDoc, ApiDocCallback, OAuth2AuthorizationEndpoint, OAuth2TokenEndpoint, Route, SimpleRoute,
32+
UrlBuilder,
33+
};
34+
use mas_storage::BoxRng;
35+
use mas_templates::{ApiDocContext, Templates};
2436
use tower_http::cors::{Any, CorsLayer};
2537

2638
mod call_context;
2739
mod model;
2840
mod params;
2941
mod response;
42+
mod schema;
3043
mod v1;
3144

3245
use self::call_context::CallContext;
3346

3447
pub fn router<S>() -> (OpenApi, Router<S>)
3548
where
3649
S: Clone + Send + Sync + 'static,
50+
BoxHomeserverConnection: FromRef<S>,
51+
BoxRng: FromRequestParts<S>,
3752
CallContext: FromRequestParts<S>,
53+
Templates: FromRef<S>,
54+
UrlBuilder: FromRef<S>,
3855
{
56+
aide::gen::in_context(|ctx| {
57+
ctx.schema = schemars::gen::SchemaGenerator::new(schemars::gen::SchemaSettings::openapi3());
58+
});
59+
3960
let mut api = OpenApi::default();
4061
let router = ApiRouter::<S>::new()
4162
.nest("/api/admin/v1", self::v1::router())
4263
.finish_api_with(&mut api, |t| {
4364
t.title("Matrix Authentication Service admin API")
65+
.tag(Tag {
66+
name: "user".to_owned(),
67+
description: Some("Manage users".to_owned()),
68+
..Tag::default()
69+
})
4470
.security_scheme(
4571
"oauth2",
4672
SecurityScheme::OAuth2 {
@@ -70,34 +96,62 @@ where
7096
},
7197
)
7298
.security_requirement_scopes("oauth2", ["urn:mas:admin"])
73-
.server(Server {
74-
url: "{base}".to_owned(),
75-
variables: IndexMap::from([(
76-
"base".to_owned(),
77-
ServerVariable {
78-
default: "/".to_owned(),
79-
..ServerVariable::default()
80-
},
81-
)]),
82-
..Server::default()
83-
})
8499
});
85100

86101
let router = router
87102
// Serve the OpenAPI spec as JSON
88103
.route(
89104
"/api/spec.json",
90105
axum::routing::get({
91-
let res = Json(api.clone());
92-
move || std::future::ready(res.clone())
106+
let api = api.clone();
107+
move |State(url_builder): State<UrlBuilder>| {
108+
// Let's set the servers to the HTTP base URL
109+
let mut api = api.clone();
110+
api.servers = vec![Server {
111+
url: url_builder.http_base().to_string(),
112+
..Server::default()
113+
}];
114+
115+
std::future::ready(Json(api))
116+
}
93117
}),
94118
)
119+
// Serve the Swagger API reference
120+
.route(ApiDoc::route(), axum::routing::get(swagger))
121+
.route(
122+
ApiDocCallback::route(),
123+
axum::routing::get(swagger_callback),
124+
)
95125
.layer(
96126
CorsLayer::new()
97127
.allow_origin(Any)
98128
.allow_methods(Any)
99-
.allow_otel_headers([AUTHORIZATION, ACCEPT, CONTENT_TYPE]),
129+
.allow_otel_headers([
130+
AUTHORIZATION,
131+
ACCEPT,
132+
CONTENT_TYPE,
133+
// Swagger will send this header, so we have to allow it to avoid CORS errors
134+
HeaderName::from_static("x-requested-with"),
135+
]),
100136
);
101137

102138
(api, router)
103139
}
140+
141+
async fn swagger(
142+
State(url_builder): State<UrlBuilder>,
143+
State(templates): State<Templates>,
144+
) -> Result<Html<String>, FancyError> {
145+
let ctx = ApiDocContext::from_url_builder(&url_builder);
146+
let res = templates.render_swagger(&ctx)?;
147+
Ok(Html(res))
148+
}
149+
150+
async fn swagger_callback(
151+
State(url_builder): State<UrlBuilder>,
152+
State(templates): State<Templates>,
153+
) -> Result<Html<String>, FancyError> {
154+
let ctx = ApiDocContext::from_url_builder(&url_builder);
155+
let res = templates.render_swagger_callback(&ctx)?;
156+
Ok(Html(res))
157+
}

crates/handlers/src/admin/params.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,8 @@ impl IntoResponse for UlidPathParamRejection {
5252

5353
#[derive(JsonSchema, Debug, Clone, Copy, Deserialize)]
5454
struct UlidInPath {
55-
#[schemars(
56-
with = "String",
57-
title = "ULID",
58-
description = "A ULID as per https://github.com/ulid/spec",
59-
regex(pattern = r"^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$")
60-
)]
55+
/// # The ID of the resource
56+
#[schemars(with = "super::schema::Ulid")]
6157
id: Ulid,
6258
}
6359

@@ -81,12 +77,12 @@ const DEFAULT_PAGE_SIZE: usize = 10;
8177
struct PaginationParams {
8278
/// Retrieve the items before the given ID
8379
#[serde(rename = "page[before]")]
84-
#[schemars(with = "Option<String>")]
80+
#[schemars(with = "Option<super::schema::Ulid>")]
8581
before: Option<Ulid>,
8682

8783
/// Retrieve the items after the given ID
8884
#[serde(rename = "page[after]")]
89-
#[schemars(with = "Option<String>")]
85+
#[schemars(with = "Option<super::schema::Ulid>")]
9086
after: Option<Ulid>,
9187

9288
/// Retrieve the first N items

crates/handlers/src/admin/response.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ struct SingleResource<T> {
143143
type_: &'static str,
144144

145145
/// The ID of the resource
146-
#[schemars(with = "String")]
146+
#[schemars(with = "super::schema::Ulid")]
147147
id: Ulid,
148148

149149
/// The attributes of the resource
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2024 The Matrix.org Foundation C.I.C.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Common schema definitions
16+
17+
use schemars::{
18+
gen::SchemaGenerator,
19+
schema::{InstanceType, Metadata, Schema, SchemaObject, StringValidation},
20+
JsonSchema,
21+
};
22+
23+
/// A type to use for schema definitions of ULIDs
24+
///
25+
/// Use with `#[schemars(with = "crate::admin::schema::Ulid")]`
26+
pub struct Ulid;
27+
28+
impl JsonSchema for Ulid {
29+
fn schema_name() -> String {
30+
"ULID".to_owned()
31+
}
32+
33+
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
34+
SchemaObject {
35+
instance_type: Some(InstanceType::String.into()),
36+
37+
metadata: Some(Box::new(Metadata {
38+
title: Some("ULID".into()),
39+
description: Some("A ULID as per https://github.com/ulid/spec".into()),
40+
examples: vec![
41+
"01ARZ3NDEKTSV4RRFFQ69G5FAV".into(),
42+
"01J41912SC8VGAQDD50F6APK91".into(),
43+
],
44+
..Metadata::default()
45+
})),
46+
47+
string: Some(Box::new(StringValidation {
48+
pattern: Some(r"^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$".into()),
49+
..StringValidation::default()
50+
})),
51+
52+
..SchemaObject::default()
53+
}
54+
.into()
55+
}
56+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
// limitations under the License.
1414

1515
use aide::axum::{routing::get_with, ApiRouter};
16-
use axum::extract::FromRequestParts;
16+
use axum::extract::{FromRef, FromRequestParts};
17+
use mas_matrix::BoxHomeserverConnection;
18+
use mas_storage::BoxRng;
1719

1820
use super::call_context::CallContext;
1921

@@ -22,10 +24,16 @@ mod users;
2224
pub fn router<S>() -> ApiRouter<S>
2325
where
2426
S: Clone + Send + Sync + 'static,
27+
BoxHomeserverConnection: FromRef<S>,
28+
BoxRng: FromRequestParts<S>,
2529
CallContext: FromRequestParts<S>,
2630
{
2731
ApiRouter::<S>::new()
28-
.api_route("/users", get_with(self::users::list, self::users::list_doc))
32+
.api_route(
33+
"/users",
34+
get_with(self::users::list, self::users::list_doc)
35+
.post_with(self::users::add, self::users::add_doc),
36+
)
2937
.api_route(
3038
"/users/:id",
3139
get_with(self::users::get, self::users::get_doc),

0 commit comments

Comments
 (0)