From 47a1aae75c8c04ab6665ed38dee0f161fa060044 Mon Sep 17 00:00:00 2001 From: Khor Shu Heng <32997938+khorshuheng@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:01:22 +0800 Subject: [PATCH] feat: Endpoint for server info (#835) * feat: provide endpoint so that client can detect server information * feat: add server info endpoint --- libs/client-api/src/http.rs | 23 +++++++++++++++++++ libs/shared-entity/Cargo.toml | 13 ++++++++--- libs/shared-entity/src/dto/mod.rs | 1 + libs/shared-entity/src/dto/server_info_dto.rs | 13 +++++++++++ src/api/mod.rs | 1 + src/api/server_info.rs | 18 +++++++++++++++ src/application.rs | 2 ++ tests/main.rs | 1 + tests/server_info/info.rs | 9 ++++++++ tests/server_info/mod.rs | 1 + 10 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 libs/shared-entity/src/dto/server_info_dto.rs create mode 100644 src/api/server_info.rs create mode 100644 tests/server_info/info.rs create mode 100644 tests/server_info/mod.rs diff --git a/libs/client-api/src/http.rs b/libs/client-api/src/http.rs index 2d65565c..c8c30bbb 100644 --- a/libs/client-api/src/http.rs +++ b/libs/client-api/src/http.rs @@ -1,6 +1,8 @@ use crate::notify::{ClientToken, TokenStateReceiver}; use app_error::AppError; +use app_error::ErrorCode; use client_api_entity::auth_dto::DeleteUserQuery; +use client_api_entity::server_info_dto::ServerInfoResponseItem; use client_api_entity::workspace_dto::FolderView; use client_api_entity::workspace_dto::QueryWorkspaceFolder; use client_api_entity::workspace_dto::QueryWorkspaceParam; @@ -11,6 +13,7 @@ use gotrue::grant::PasswordGrant; use gotrue::grant::{Grant, RefreshTokenGrant}; use gotrue::params::MagicLinkParams; use gotrue::params::{AdminUserParams, GenerateLinkParams}; +use reqwest::StatusCode; use shared_entity::dto::workspace_dto::{CreateWorkspaceParam, PatchWorkspaceParam}; use std::fmt::{Display, Formatter}; #[cfg(feature = "enable_brotli")] @@ -967,6 +970,26 @@ impl Client { .into_data() } + #[instrument(level = "info", skip_all)] + pub async fn get_server_info(&self) -> Result { + let url = format!("{}/api/server", self.base_url); + let resp = self + .http_client_with_auth(Method::GET, &url) + .await? + .send() + .await?; + if resp.status() == StatusCode::NOT_FOUND { + Err(AppResponseError::new( + ErrorCode::Unhandled, + "server info not implemented", + )) + } else { + AppResponse::::from_response(resp) + .await? + .into_data() + } + } + // Refresh token if given timestamp is close to the token expiration time pub async fn refresh_if_expired(&self, ts: i64, reason: &str) -> Result<(), AppResponseError> { let expires_at = self.token_expires_at()?; diff --git a/libs/shared-entity/Cargo.toml b/libs/shared-entity/Cargo.toml index fed5c729..b5a66090 100644 --- a/libs/shared-entity/Cargo.toml +++ b/libs/shared-entity/Cargo.toml @@ -20,11 +20,18 @@ database-entity.workspace = true collab-entity = { workspace = true } app-error = { workspace = true } chrono = "0.4.31" -appflowy-ai-client = { workspace = true, default-features = false, features = ["dto"] } +appflowy-ai-client = { workspace = true, default-features = false, features = [ + "dto", +] } pin-project = "1.1.5" -actix-web = { version = "4.4.1", default-features = false, features = ["http2"], optional = true } -validator = { version = "0.16", features = ["validator_derive", "derive"], optional = true } +actix-web = { version = "4.4.1", default-features = false, features = [ + "http2", +], optional = true } +validator = { version = "0.16", features = [ + "validator_derive", + "derive", +], optional = true } futures = "0.3.30" bytes = "1.6.0" log = "0.4.21" diff --git a/libs/shared-entity/src/dto/mod.rs b/libs/shared-entity/src/dto/mod.rs index 339e7b89..ad001138 100644 --- a/libs/shared-entity/src/dto/mod.rs +++ b/libs/shared-entity/src/dto/mod.rs @@ -5,4 +5,5 @@ pub mod billing_dto; pub mod history_dto; pub mod publish_dto; pub mod search_dto; +pub mod server_info_dto; pub mod workspace_dto; diff --git a/libs/shared-entity/src/dto/server_info_dto.rs b/libs/shared-entity/src/dto/server_info_dto.rs new file mode 100644 index 00000000..04f49d6c --- /dev/null +++ b/libs/shared-entity/src/dto/server_info_dto.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum SupportedClientFeatures { + // Supports Collab Params serialization using Protobuf + CollabParamsProtobuf, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ServerInfoResponseItem { + pub supported_client_features: Vec, + pub minimum_supported_client_version: Option, +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 74935b68..029028c5 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -6,6 +6,7 @@ pub mod access_request; pub mod history; pub mod metrics; pub mod search; +pub mod server_info; pub mod template; pub mod user; pub mod util; diff --git a/src/api/server_info.rs b/src/api/server_info.rs new file mode 100644 index 00000000..39fe076c --- /dev/null +++ b/src/api/server_info.rs @@ -0,0 +1,18 @@ +use actix_web::{web, Scope}; +use shared_entity::dto::server_info_dto::ServerInfoResponseItem; +use shared_entity::response::{AppResponse, JsonAppResponse}; + +pub fn server_info_scope() -> Scope { + web::scope("/api/server").service(web::resource("").route(web::get().to(server_info_handler))) +} + +async fn server_info_handler() -> actix_web::Result> { + Ok( + AppResponse::Ok() + .with_data(ServerInfoResponseItem { + supported_client_features: vec![], + minimum_supported_client_version: None, + }) + .into(), + ) +} diff --git a/src/application.rs b/src/application.rs index 52e9d876..192f22fb 100644 --- a/src/application.rs +++ b/src/application.rs @@ -49,6 +49,7 @@ use crate::api::file_storage::file_storage_scope; use crate::api::history::history_scope; use crate::api::metrics::metrics_scope; use crate::api::search::search_scope; +use crate::api::server_info::server_info_scope; use crate::api::template::template_scope; use crate::api::user::user_scope; use crate::api::workspace::{collab_scope, workspace_scope}; @@ -159,6 +160,7 @@ pub async fn run_actix_server( // .wrap(DecryptPayloadMiddleware) .wrap(access_control.clone()) .wrap(RequestIdMiddleware) + .service(server_info_scope()) .service(user_scope()) .service(workspace_scope()) .service(collab_scope()) diff --git a/tests/main.rs b/tests/main.rs index 3224a624..50cec96a 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -4,6 +4,7 @@ mod collab_history; mod file_test; mod gotrue; mod search; +mod server_info; mod sql_test; mod user; mod websocket; diff --git a/tests/server_info/info.rs b/tests/server_info/info.rs new file mode 100644 index 00000000..12d25bc3 --- /dev/null +++ b/tests/server_info/info.rs @@ -0,0 +1,9 @@ +use client_api_test::generate_unique_registered_user_client; + +#[tokio::test] +async fn test_get_server_info() { + let (c, _) = generate_unique_registered_user_client().await; + c.get_server_info() + .await + .expect("Failed to get server info"); +} diff --git a/tests/server_info/mod.rs b/tests/server_info/mod.rs new file mode 100644 index 00000000..15889de4 --- /dev/null +++ b/tests/server_info/mod.rs @@ -0,0 +1 @@ +mod info;