Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions crates/cli/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{convert::Infallible, net::IpAddr, sync::Arc};
use axum::extract::{FromRef, FromRequestParts};
use ipnetwork::IpNetwork;
use mas_context::LogContext;
use mas_data_model::{BoxClock, BoxRng, SiteConfig, SystemClock};
use mas_data_model::{AppVersion, BoxClock, BoxRng, SiteConfig, SystemClock};
use mas_handlers::{
ActivityTracker, BoundActivityTracker, CookieManager, ErrorWrapper, GraphQLSchema, Limiter,
MetadataCache, RequesterFingerprint, passwords::PasswordManager,
Expand All @@ -27,7 +27,7 @@ use rand::SeedableRng;
use sqlx::PgPool;
use tracing::Instrument;

use crate::telemetry::METER;
use crate::{VERSION, telemetry::METER};

#[derive(Clone)]
pub struct AppState {
Expand Down Expand Up @@ -214,6 +214,12 @@ impl FromRef<AppState> for Arc<dyn HomeserverConnection> {
}
}

impl FromRef<AppState> for AppVersion {
fn from_ref(_input: &AppState) -> Self {
AppVersion(VERSION)
}
}

impl FromRequestParts<AppState> for BoxClock {
type Rejection = Infallible;

Expand Down
2 changes: 2 additions & 0 deletions crates/data-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) mod upstream_oauth2;
pub(crate) mod user_agent;
pub(crate) mod users;
mod utils;
mod version;

/// Error when an invalid state transition is attempted.
#[derive(Debug, Error)]
Expand Down Expand Up @@ -57,4 +58,5 @@ pub use self::{
UserRecoveryTicket, UserRegistration, UserRegistrationPassword, UserRegistrationToken,
},
utils::{BoxClock, BoxRng},
version::AppVersion,
};
8 changes: 8 additions & 0 deletions crates/data-model/src/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.

/// A structure which holds information about the running version of the app
#[derive(Debug, Clone, Copy)]
pub struct AppVersion(pub &'static str);
3 changes: 2 additions & 1 deletion crates/handlers/src/admin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use axum::{
use hyper::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use indexmap::IndexMap;
use mas_axum_utils::InternalError;
use mas_data_model::{BoxRng, SiteConfig};
use mas_data_model::{AppVersion, BoxRng, SiteConfig};
use mas_http::CorsLayerExt;
use mas_matrix::HomeserverConnection;
use mas_policy::PolicyFactory;
Expand Down Expand Up @@ -164,6 +164,7 @@ where
UrlBuilder: FromRef<S>,
Arc<PolicyFactory>: FromRef<S>,
SiteConfig: FromRef<S>,
AppVersion: FromRef<S>,
{
// We *always* want to explicitly set the possible responses, beacuse the
// infered ones are not necessarily correct
Expand Down
8 changes: 7 additions & 1 deletion crates/handlers/src/admin/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use aide::axum::{
routing::{get_with, post_with},
};
use axum::extract::{FromRef, FromRequestParts};
use mas_data_model::{BoxRng, SiteConfig};
use mas_data_model::{AppVersion, BoxRng, SiteConfig};
use mas_matrix::HomeserverConnection;
use mas_policy::PolicyFactory;

Expand All @@ -28,13 +28,15 @@ mod user_emails;
mod user_registration_tokens;
mod user_sessions;
mod users;
mod version;

pub fn router<S>() -> ApiRouter<S>
where
S: Clone + Send + Sync + 'static,
Arc<dyn HomeserverConnection>: FromRef<S>,
PasswordManager: FromRef<S>,
SiteConfig: FromRef<S>,
AppVersion: FromRef<S>,
Arc<PolicyFactory>: FromRef<S>,
BoxRng: FromRequestParts<S>,
CallContext: FromRequestParts<S>,
Expand All @@ -44,6 +46,10 @@ where
"/site-config",
get_with(self::site_config::handler, self::site_config::doc),
)
.api_route(
"/version",
get_with(self::version::handler, self::version::doc),
)
.api_route(
"/compat-sessions",
get_with(self::compat_sessions::list, self::compat_sessions::list_doc),
Expand Down
62 changes: 62 additions & 0 deletions crates/handlers/src/admin/v1/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.

use aide::transform::TransformOperation;
use axum::{Json, extract::State};
use mas_data_model::AppVersion;
use schemars::JsonSchema;
use serde::Serialize;

use crate::admin::call_context::CallContext;

#[derive(Serialize, JsonSchema)]
pub struct Version {
/// The semver version of the app
pub version: &'static str,
}

pub fn doc(operation: TransformOperation) -> TransformOperation {
operation
.id("version")
.tag("server")
.summary("Get the version currently running")
.response_with::<200, Json<Version>, _>(|t| t.example(Version { version: "v1.0.0" }))
}

#[tracing::instrument(name = "handler.admin.v1.version", skip_all)]
pub async fn handler(
_: CallContext,
State(AppVersion(version)): State<mas_data_model::AppVersion>,
) -> Json<Version> {
Json(Version { version })
}

#[cfg(test)]
mod tests {
use hyper::{Request, StatusCode};
use insta::assert_json_snapshot;
use sqlx::PgPool;

use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};

#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
async fn test_add_user(pool: PgPool) {
setup();
let mut state = TestState::from_pool(pool).await.unwrap();
let token = state.token_with_scope("urn:mas:admin").await;

let request = Request::get("/api/admin/v1/version").bearer(&token).empty();

let response = state.request(request).await;

assert_eq!(response.status(), StatusCode::OK);
let body: serde_json::Value = response.json();
assert_json_snapshot!(body, @r#"
{
"version": "v0.0.0-test"
}
"#);
}
}
1 change: 1 addition & 0 deletions crates/handlers/src/bin/api-schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl_from_ref!(mas_keystore::Keystore);
impl_from_ref!(mas_handlers::passwords::PasswordManager);
impl_from_ref!(Arc<mas_policy::PolicyFactory>);
impl_from_ref!(mas_data_model::SiteConfig);
impl_from_ref!(mas_data_model::AppVersion);

fn main() -> Result<(), Box<dyn std::error::Error>> {
let (mut api, _) = mas_handlers::admin_api_router::<DummyState>();
Expand Down
8 changes: 7 additions & 1 deletion crates/handlers/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use mas_axum_utils::{
cookies::{CookieJar, CookieManager},
};
use mas_config::RateLimitingConfig;
use mas_data_model::{BoxClock, BoxRng, SiteConfig, clock::MockClock};
use mas_data_model::{AppVersion, BoxClock, BoxRng, SiteConfig, clock::MockClock};
use mas_email::{MailTransport, Mailer};
use mas_i18n::Translator;
use mas_keystore::{Encrypter, JsonWebKey, JsonWebKeySet, Keystore, PrivateKey};
Expand Down Expand Up @@ -575,6 +575,12 @@ impl FromRef<TestState> for reqwest::Client {
}
}

impl FromRef<TestState> for AppVersion {
fn from_ref(_input: &TestState) -> Self {
AppVersion("v0.0.0-test")
}
}

impl FromRequestParts<TestState> for ActivityTracker {
type Rejection = Infallible;

Expand Down
36 changes: 36 additions & 0 deletions docs/api/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,30 @@
}
}
},
"/api/admin/v1/version": {
"get": {
"tags": [
"server"
],
"summary": "Get the version currently running",
"operationId": "version",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Version"
},
"example": {
"version": "v1.0.0"
}
}
}
}
}
}
},
"/api/admin/v1/compat-sessions": {
"get": {
"tags": [
Expand Down Expand Up @@ -3710,6 +3734,18 @@
}
}
},
"Version": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"description": "The semver version of the app",
"type": "string"
}
}
},
"PaginationParams": {
"type": "object",
"properties": {
Expand Down
Loading