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
2 changes: 1 addition & 1 deletion .github/workflows/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:
- 'scripts/**'
- 'third-party/**'
- 'toolchains/**'
- 'workflows/web-test*'
- '.github/workflows/web-test*'

name: Base GitHub Action for Check, Test and Lints

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ aws-config = "1.6.0"
aws-sdk-s3 = "1.79.0"
quinn = "0.11"
utoipa = "5.3"
utoipa-axum = "0.2.0"
utoipa-swagger-ui = "9.0"

rdkafka = "0.37"
threadpool = "1.8.1"
lru-mem = "0.3.0"
Expand Down
10 changes: 5 additions & 5 deletions ceres/src/model/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use mercury::internal::object::{
tree::{TreeItem, TreeItemMode},
};

#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize)]
#[derive(PartialEq, Eq, Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct CreateFileInfo {
/// can be a file or directory
pub is_directory: bool,
Expand All @@ -32,13 +32,13 @@ pub struct TreeQuery {
pub path: String,
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, IntoParams)]
pub struct BlobContentQuery {
#[serde(default = "default_path")]
pub path: String,
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, ToSchema)]
pub struct LatestCommitInfo {
pub oid: String,
pub date: String,
Expand All @@ -48,7 +48,7 @@ pub struct LatestCommitInfo {
pub status: String,
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, ToSchema)]
pub struct UserInfo {
pub display_name: String,
pub avatar_url: String,
Expand All @@ -63,7 +63,7 @@ impl Default for UserInfo {
}
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, ToSchema)]
pub struct TreeCommitItem {
pub oid: String,
pub name: String,
Expand Down
4 changes: 0 additions & 4 deletions docker/mono-pg-dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,4 @@ ENV POSTGRES_PASSWORD=mono

EXPOSE 5432

# Add the database initialization script to the container
# When the container starts, PostgreSQL will automatically execute all .sql files in the docker-entrypoint-initdb.d/ directory
COPY ./sql/postgres/pg_20241204__init.sql /docker-entrypoint-initdb.d/

CMD ["postgres"]
3 changes: 3 additions & 0 deletions gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ reqwest = { workspace = true, features = ["json"] }
lazy_static = { workspace = true }
chrono = { workspace = true }
quinn = { workspace = true }
utoipa = { workspace = true, features = ["axum_extras"] }
utoipa-axum = { workspace = true }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
7 changes: 4 additions & 3 deletions gateway/src/api/github_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use crate::api::MegaApiServiceState;
use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse;
use axum::routing::post;
use axum::{Json, Router};
use axum::Json;
use lazy_static::lazy_static;
use reqwest::Client;
use serde_json::Value;
use utoipa_axum::router::OpenApiRouter;

lazy_static! {
static ref CLIENT: Client = Client::builder()
Expand All @@ -14,8 +15,8 @@ lazy_static! {
.unwrap();
}

pub fn routers() -> Router<MegaApiServiceState> {
Router::new().route("/github/webhook", post(webhook))
pub fn routers() -> OpenApiRouter<MegaApiServiceState> {
OpenApiRouter::new().route("/github/webhook", post(webhook))
}

/// Handle the GitHub webhook event. <br>
Expand Down
15 changes: 11 additions & 4 deletions gateway/src/https_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use jupiter::context::Context;
use mono::api::lfs::lfs_router;
use mono::api::MonoApiServiceState;
use mono::server::https_server::{get_method_router, post_method_router, AppState};
use utoipa::OpenApi;
use utoipa_axum::router::OpenApiRouter;

use crate::api::{github_router, MegaApiServiceState};

Expand Down Expand Up @@ -66,17 +68,17 @@ pub async fn app(context: Context, host: String, port: u16, p2p: P2pOptions) ->
p2p,
};

pub fn mega_routers() -> Router<MegaApiServiceState> {
Router::new().merge(github_router::routers())
pub fn mega_routers() -> OpenApiRouter<MegaApiServiceState> {
OpenApiRouter::new().merge(github_router::routers())
}

// add RequestDecompressionLayer for handle gzip encode
// add TraceLayer for log record
// add CorsLayer to add cors header
Router::new()
let (router, _) = OpenApiRouter::with_openapi(ApiDoc::openapi())
.merge(lfs_router::routers().with_state(mono_api_state.clone()))
.merge(
Router::new()
OpenApiRouter::new()
.nest(
"/api/v1/mono",
mono::api::api_router::routers().with_state(mono_api_state.clone()),
Expand All @@ -97,6 +99,8 @@ pub async fn app(context: Context, host: String, port: u16, p2p: P2pOptions) ->
.layer(TraceLayer::new_for_http())
.layer(RequestDecompressionLayer::new())
.with_state(state)
.split_for_parts();
router
}

pub fn check_run_with_p2p(context: Context, p2p: P2pOptions) {
Expand All @@ -116,5 +120,8 @@ pub fn check_run_with_p2p(context: Context, p2p: P2pOptions) {
};
}

#[derive(OpenApi)]

Copilot AI May 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] ApiDoc is derived but no paths or tags are specified, so the generated OpenAPI document will be empty. Add a #[openapi(paths(...), tags(...))] attribute to include gateway endpoints.

Suggested change
#[derive(OpenApi)]
#[derive(OpenApi)]
#[openapi(
paths(
github_router::routers,
lfs_router::routers,
mono::api::api_router::routers
),
tags(
(name = "GitHub", description = "GitHub-related endpoints"),
(name = "LFS", description = "Large File Storage endpoints"),
(name = "Mono", description = "Mono API endpoints")
)
)]

Copilot uses AI. Check for mistakes.
struct ApiDoc;

#[cfg(test)]
mod tests {}
2 changes: 2 additions & 0 deletions mono/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ async-session = { workspace = true }
http = { workspace = true }
cedar-policy = { workspace = true }
utoipa = { workspace = true, features = ["axum_extras"] }
utoipa-axum = { workspace = true }
utoipa-swagger-ui = { workspace = true, features = ["axum"] }

[target.'cfg(not(windows))'.dependencies]
jemallocator = { workspace = true }
Expand Down
121 changes: 87 additions & 34 deletions mono/src/api/api_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use axum::{
body::Body,
extract::{Path, Query, State},
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
routing::get,
Json,
};
use http::StatusCode;

Expand All @@ -17,31 +17,42 @@ use ceres::{
},
};
use common::model::CommonResult;
use utoipa_axum::{router::OpenApiRouter, routes};

use crate::api::error::ApiError;
use crate::api::issue::issue_router;
use crate::api::mr::mr_router;
use crate::api::user::user_router;
use crate::api::MonoApiServiceState;

pub fn routers() -> Router<MonoApiServiceState> {
let router = Router::new()
.route("/status", get(life_cycle_check))
.route("/create-file", post(create_file))
.route("/latest-commit", get(get_latest_commit))
.route("/tree/commit-info", get(get_tree_commit_info))
.route("/tree/path-can-clone", get(path_can_be_cloned))
.route("/tree", get(get_tree_info))
.route("/blob", get(get_blob_string))
use crate::{api::error::ApiError, server::https_server::GIT_TAG};

pub fn routers() -> OpenApiRouter<MonoApiServiceState> {
OpenApiRouter::new()
.routes(routes!(life_cycle_check))
.routes(routes!(create_file))
.routes(routes!(get_latest_commit))
.routes(routes!(get_tree_commit_info))
.routes(routes!(path_can_be_cloned))
.routes(routes!(get_tree_info))
.routes(routes!(get_blob_string))
.route("/file/blob/{object_id}", get(get_blob_file))
.route("/file/tree", get(get_tree_file));
Router::new()
.merge(router)
.route("/file/tree", get(get_tree_file))
.merge(mr_router::routers())
.merge(user_router::routers())
.merge(issue_router::routers())
}

/// Get blob file as string
#[utoipa::path(
get,
params(
BlobContentQuery
),
path = "/blob",
responses(
(status = 200, body = CommonResult<String>, content_type = "application/json")
),
tag = GIT_TAG
)]
async fn get_blob_string(
Query(query): Query<BlobContentQuery>,
state: State<MonoApiServiceState>,
Expand All @@ -54,10 +65,29 @@ async fn get_blob_string(
Ok(Json(CommonResult::success(data)))
}

/// Health Check
#[utoipa::path(
get,
path = "/status",
responses(
(status = 200, body = str, content_type = "text/plain")
),
tag = GIT_TAG
)]
async fn life_cycle_check() -> Result<impl IntoResponse, ApiError> {
Ok(Json("http ready"))
}

/// Create file in web UI
#[utoipa::path(
get,

Copilot AI May 20, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint uses GET but also accepts a request body (CreateFileInfo). According to HTTP semantics, GET should not have a body—consider switching to POST or another appropriate verb for creating resources.

Suggested change
get,
post,

Copilot uses AI. Check for mistakes.
path = "/create-file",
request_body = CreateFileInfo,
responses(
(status = 200, body = CommonResult<String>, content_type = "application/json")
),
tag = GIT_TAG
)]
async fn create_file(
state: State<MonoApiServiceState>,
Json(json): Json<CreateFileInfo>,
Expand All @@ -70,6 +100,18 @@ async fn create_file(
Ok(Json(CommonResult::success(None)))
}

/// Get latest commit by path
#[utoipa::path(
get,
path = "/latest-commit",
params(
CodePreviewQuery
),
responses(
(status = 200, body = LatestCommitInfo, content_type = "application/json")
),
tag = GIT_TAG
)]
async fn get_latest_commit(
Query(query): Query<CodePreviewQuery>,
state: State<MonoApiServiceState>,
Expand All @@ -82,6 +124,18 @@ async fn get_latest_commit(
Ok(Json(res))
}

/// Get tree brief info
#[utoipa::path(
get,
path = "/tree",
params(
CodePreviewQuery
),
responses(
(status = 200, body = CommonResult<Vec<TreeBriefItem>>, content_type = "application/json")
),
tag = GIT_TAG
)]
async fn get_tree_info(
Query(query): Query<CodePreviewQuery>,
state: State<MonoApiServiceState>,
Expand All @@ -94,17 +148,17 @@ async fn get_tree_info(
Ok(Json(CommonResult::success(Some(data))))
}

/// List matching trees with commit msg by query
#[utoipa::path(
get,
path = "api/v1/tree/commit-info",
path = "/tree/commit-info",
params(
CodePreviewQuery
),
responses(
(status = 200, description = "List matching trees by query",
body = CommonResult<Vec<TreeBriefItem>>,
content_type = "application/json")
)
(status = 200, body = CommonResult<Vec<TreeCommitItem>>, content_type = "application/json")
),
tag = GIT_TAG
)]
async fn get_tree_commit_info(
Query(query): Query<CodePreviewQuery>,
Expand Down Expand Up @@ -159,6 +213,18 @@ pub async fn get_tree_file(
.unwrap())
}

/// Check if a path can be cloned
#[utoipa::path(
get,
path = "/tree/path-can-clone",
params(
BlobContentQuery
),
responses(
(status = 200, body = CommonResult<bool>, content_type = "application/json")
),
tag = GIT_TAG
)]
async fn path_can_be_cloned(
Query(query): Query<BlobContentQuery>,
state: State<MonoApiServiceState>,
Expand All @@ -180,16 +246,3 @@ async fn path_can_be_cloned(
};
Ok(Json(CommonResult::success(Some(res))))
}

#[cfg(test)]
mod test {
use utoipa::OpenApi;

#[test]
fn generate_swagger_json() {
#[derive(OpenApi)]
#[openapi(paths(crate::api::api_router::get_tree_commit_info))]
struct ApiDoc;
println!("{}", ApiDoc::openapi().to_pretty_json().unwrap());
}
}
9 changes: 5 additions & 4 deletions mono/src/api/issue/issue_router.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use axum::{
extract::{Path, State},
routing::{get, post},
Json, Router,
Json,
};
use bytes::Bytes;
use serde::Deserialize;
use utoipa_axum::router::OpenApiRouter;

use common::model::{CommonPage, CommonResult, PageParams};

Expand All @@ -13,10 +14,10 @@ use crate::api::issue::{IssueDetail, IssueItem, NewIssue};
use crate::api::oauth::model::LoginUser;
use crate::api::MonoApiServiceState;

pub fn routers() -> Router<MonoApiServiceState> {
Router::new().nest(
pub fn routers() -> OpenApiRouter<MonoApiServiceState> {
OpenApiRouter::new().nest(
"/issue",
Router::new()
OpenApiRouter::new()
.route("/list", post(fetch_issue_list))
.route("/new", post(new_issue))
.route("/{link}/close", post(close_issue))
Expand Down
Loading