Added swagger ui support
This commit is contained in:
Generated
+55
@@ -38,6 +38,21 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aide"
|
||||||
|
version = "0.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6966317188cdfe54c58c0900a195d021294afb3ece9b7073d09e4018dbb1e3a2"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"indexmap 2.14.0",
|
||||||
|
"schemars 0.9.0",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -509,6 +524,7 @@ name = "cot"
|
|||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
|
"aide",
|
||||||
"askama",
|
"askama",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -536,12 +552,14 @@ dependencies = [
|
|||||||
"multer",
|
"multer",
|
||||||
"password-auth",
|
"password-auth",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"schemars 0.9.0",
|
||||||
"sea-query",
|
"sea-query",
|
||||||
"sea-query-binder",
|
"sea-query-binder",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
"swagger-ui-redist",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -580,6 +598,7 @@ dependencies = [
|
|||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
|
"schemars 0.9.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_html_form",
|
"serde_html_form",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1066,6 +1085,7 @@ dependencies = [
|
|||||||
"cot",
|
"cot",
|
||||||
"openidconnect",
|
"openidconnect",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"schemars 0.9.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2524,7 +2544,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
|
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
|
"indexmap 2.14.0",
|
||||||
"ref-cast",
|
"ref-cast",
|
||||||
|
"schemars_derive",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
@@ -2541,6 +2563,18 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schemars_derive"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5016d94c77c6d32f0b8e08b781f7dc8a90c2007d4e77472cc2807bc10a8438fe"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde_derive_internals",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -2628,6 +2662,17 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive_internals"
|
||||||
|
version = "0.29.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_html_form"
|
name = "serde_html_form"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -3053,6 +3098,16 @@ version = "2.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swagger-ui-redist"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cfaaf9b731f41f15b1a7d01419e6ff9ff59eef7bc6c04eae04911e3dbe5e17a"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,8 @@ edition = "2024"
|
|||||||
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cot = { path = "../cot/cot", features = ["postgres", "json"] }
|
cot = { path = "../cot/cot", features = ["postgres", "json", "openapi", "swagger-ui"] }
|
||||||
|
schemars = { version = "0.9", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
openidconnect = "4.0"
|
openidconnect = "4.0"
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
|
||||||
|
|||||||
@@ -126,7 +126,13 @@ Helpers in `api/mod.rs`:
|
|||||||
|-------|--------|-------------|
|
|-------|--------|-------------|
|
||||||
| `/api/me` | GET | Current user (id, name, role) or 401 |
|
| `/api/me` | GET | Current user (id, name, role) or 401 |
|
||||||
|
|
||||||
To add a new API endpoint: write an async handler returning `cot::Result<cot::response::Response>`, use `json_ok`/`json_error`, add a `Route` in `ApiApp::router()`.
|
**Swagger UI** is available at `/swagger/` when `FURU_SWAGGER_ENABLED=true`. The OpenAPI spec is auto-generated from handler types.
|
||||||
|
|
||||||
|
To add a new API endpoint:
|
||||||
|
1. Define request/response structs with `#[derive(Serialize, JsonSchema)]`
|
||||||
|
2. Write an async handler, return `Json(response).into_response()`
|
||||||
|
3. Add a `Route::with_api_handler_and_name(…, api_get(handler), …)` in `ApiApp::router()`
|
||||||
|
4. The endpoint appears automatically in Swagger UI
|
||||||
|
|
||||||
### Admin panel (`src/admin/`)
|
### Admin panel (`src/admin/`)
|
||||||
|
|
||||||
@@ -191,3 +197,4 @@ All prefixed with `FURU_`. Priority: env var > DB override > compiled default.
|
|||||||
| `FURU_OIDC_CLIENT_SECRET` | OIDC client secret | *(empty)* |
|
| `FURU_OIDC_CLIENT_SECRET` | OIDC client secret | *(empty)* |
|
||||||
| `FURU_OIDC_BUTTON_TEXT` | SSO button label | `Sign in with SSO` |
|
| `FURU_OIDC_BUTTON_TEXT` | SSO button label | `Sign in with SSO` |
|
||||||
| `FURU_OIDC_ADMIN_GROUPS` | Comma-separated OIDC groups that grant admin | *(empty)* |
|
| `FURU_OIDC_ADMIN_GROUPS` | Comma-separated OIDC groups that grant admin | *(empty)* |
|
||||||
|
| `FURU_SWAGGER_ENABLED` | Serve Swagger UI at `/swagger/` | `false` |
|
||||||
|
|||||||
+9
-1
@@ -86,6 +86,7 @@ fn config_display_entries(config: &AppConfig, sources: &ConfigSources) -> Vec<Co
|
|||||||
entry!(auth_sso_enabled, config.auth_sso_enabled.to_string(), defaults.auth_sso_enabled.to_string()),
|
entry!(auth_sso_enabled, config.auth_sso_enabled.to_string(), defaults.auth_sso_enabled.to_string()),
|
||||||
entry!(oidc_button_text, config.oidc_button_text.clone(), defaults.oidc_button_text.clone()),
|
entry!(oidc_button_text, config.oidc_button_text.clone(), defaults.oidc_button_text.clone()),
|
||||||
entry!(oidc_admin_groups, config.oidc_admin_groups.clone(), defaults.oidc_admin_groups.clone()),
|
entry!(oidc_admin_groups, config.oidc_admin_groups.clone(), defaults.oidc_admin_groups.clone()),
|
||||||
|
entry!(swagger_enabled, config.swagger_enabled.to_string(), defaults.swagger_enabled.to_string()),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +156,8 @@ struct SettingsTemplate {
|
|||||||
oidc_client_secret_source: &'static str,
|
oidc_client_secret_source: &'static str,
|
||||||
oidc_admin_groups: String,
|
oidc_admin_groups: String,
|
||||||
oidc_admin_groups_source: &'static str,
|
oidc_admin_groups_source: &'static str,
|
||||||
|
swagger_enabled: bool,
|
||||||
|
swagger_enabled_source: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn settings_handler(
|
pub async fn settings_handler(
|
||||||
@@ -185,6 +188,8 @@ pub async fn settings_handler(
|
|||||||
oidc_client_secret_source: sources.oidc_client_secret.code(),
|
oidc_client_secret_source: sources.oidc_client_secret.code(),
|
||||||
oidc_admin_groups: config.oidc_admin_groups,
|
oidc_admin_groups: config.oidc_admin_groups,
|
||||||
oidc_admin_groups_source: sources.oidc_admin_groups.code(),
|
oidc_admin_groups_source: sources.oidc_admin_groups.code(),
|
||||||
|
swagger_enabled: config.swagger_enabled,
|
||||||
|
swagger_enabled_source: sources.swagger_enabled.code(),
|
||||||
};
|
};
|
||||||
Ok(Html::new(template.render()?))
|
Ok(Html::new(template.render()?))
|
||||||
}
|
}
|
||||||
@@ -198,6 +203,7 @@ pub struct OidcSettingsForm {
|
|||||||
oidc_client_id: String,
|
oidc_client_id: String,
|
||||||
oidc_client_secret: String,
|
oidc_client_secret: String,
|
||||||
oidc_admin_groups: String,
|
oidc_admin_groups: String,
|
||||||
|
swagger_enabled: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn settings_submit(
|
pub async fn settings_submit(
|
||||||
@@ -212,7 +218,8 @@ pub async fn settings_submit(
|
|||||||
FormResult::Ok(data) => {
|
FormResult::Ok(data) => {
|
||||||
let pw_enabled = if data.auth_password_enabled.is_some() { "true" } else { "false" };
|
let pw_enabled = if data.auth_password_enabled.is_some() { "true" } else { "false" };
|
||||||
let sso_enabled = if data.auth_sso_enabled.is_some() { "true" } else { "false" };
|
let sso_enabled = if data.auth_sso_enabled.is_some() { "true" } else { "false" };
|
||||||
let fields: [(&str, &str); 7] = [
|
let swagger = if data.swagger_enabled.is_some() { "true" } else { "false" };
|
||||||
|
let fields: [(&str, &str); 8] = [
|
||||||
("auth_password_enabled", pw_enabled),
|
("auth_password_enabled", pw_enabled),
|
||||||
("auth_sso_enabled", sso_enabled),
|
("auth_sso_enabled", sso_enabled),
|
||||||
("oidc_button_text", &data.oidc_button_text),
|
("oidc_button_text", &data.oidc_button_text),
|
||||||
@@ -220,6 +227,7 @@ pub async fn settings_submit(
|
|||||||
("oidc_client_id", &data.oidc_client_id),
|
("oidc_client_id", &data.oidc_client_id),
|
||||||
("oidc_client_secret", &data.oidc_client_secret),
|
("oidc_client_secret", &data.oidc_client_secret),
|
||||||
("oidc_admin_groups", &data.oidc_admin_groups),
|
("oidc_admin_groups", &data.oidc_admin_groups),
|
||||||
|
("swagger_enabled", swagger),
|
||||||
];
|
];
|
||||||
for (key, value) in fields {
|
for (key, value) in fields {
|
||||||
let mut entry = ConfigEntry::new(key.to_owned(), value.to_owned());
|
let mut entry = ConfigEntry::new(key.to_owned(), value.to_owned());
|
||||||
|
|||||||
+20
-16
@@ -1,23 +1,19 @@
|
|||||||
use cot::db::Database;
|
use cot::db::Database;
|
||||||
use cot::router::method::get;
|
use cot::json::Json;
|
||||||
|
use cot::response::IntoResponse;
|
||||||
|
use cot::router::method::openapi::api_get;
|
||||||
use cot::router::{Route, Router};
|
use cot::router::{Route, Router};
|
||||||
use cot::session::Session;
|
use cot::session::Session;
|
||||||
use cot::{App, Body};
|
use cot::{App, Body};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::auth;
|
use crate::auth;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// JSON response helpers
|
// JSON error helper
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
fn json_ok(value: &serde_json::Value) -> cot::response::Response {
|
|
||||||
cot::http::Response::builder()
|
|
||||||
.status(cot::http::StatusCode::OK)
|
|
||||||
.header(cot::http::header::CONTENT_TYPE, "application/json")
|
|
||||||
.body(Body::fixed(value.to_string()))
|
|
||||||
.expect("valid response")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_error(status: cot::http::StatusCode, message: &str) -> cot::response::Response {
|
fn json_error(status: cot::http::StatusCode, message: &str) -> cot::response::Response {
|
||||||
let body = serde_json::json!({ "error": message });
|
let body = serde_json::json!({ "error": message });
|
||||||
cot::http::Response::builder()
|
cot::http::Response::builder()
|
||||||
@@ -31,6 +27,13 @@ fn json_error(status: cot::http::StatusCode, message: &str) -> cot::response::Re
|
|||||||
// GET /api/me
|
// GET /api/me
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, JsonSchema)]
|
||||||
|
struct MeResponse {
|
||||||
|
id: i64,
|
||||||
|
name: String,
|
||||||
|
role: String,
|
||||||
|
}
|
||||||
|
|
||||||
async fn me_handler(
|
async fn me_handler(
|
||||||
session: Session,
|
session: Session,
|
||||||
db: Database,
|
db: Database,
|
||||||
@@ -42,11 +45,12 @@ async fn me_handler(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(json_ok(&serde_json::json!({
|
Json(MeResponse {
|
||||||
"id": user.id,
|
id: user.id,
|
||||||
"name": user.name,
|
name: user.name,
|
||||||
"role": user.role.code(),
|
role: user.role.code().to_owned(),
|
||||||
})))
|
})
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -62,7 +66,7 @@ impl App for ApiApp {
|
|||||||
|
|
||||||
fn router(&self) -> Router {
|
fn router(&self) -> Router {
|
||||||
Router::with_urls([
|
Router::with_urls([
|
||||||
Route::with_handler_and_name("/me", get(me_handler), "api_me"),
|
Route::with_api_handler_and_name("/me", api_get(me_handler), "api_me"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ pub struct ConfigSources {
|
|||||||
pub auth_sso_enabled: ConfigSource,
|
pub auth_sso_enabled: ConfigSource,
|
||||||
pub oidc_button_text: ConfigSource,
|
pub oidc_button_text: ConfigSource,
|
||||||
pub oidc_admin_groups: ConfigSource,
|
pub oidc_admin_groups: ConfigSource,
|
||||||
|
pub swagger_enabled: ConfigSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ConfigSources {
|
impl Default for ConfigSources {
|
||||||
@@ -141,6 +142,7 @@ impl Default for ConfigSources {
|
|||||||
auth_sso_enabled: ConfigSource::Default,
|
auth_sso_enabled: ConfigSource::Default,
|
||||||
oidc_button_text: ConfigSource::Default,
|
oidc_button_text: ConfigSource::Default,
|
||||||
oidc_admin_groups: ConfigSource::Default,
|
oidc_admin_groups: ConfigSource::Default,
|
||||||
|
swagger_enabled: ConfigSource::Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,6 +225,8 @@ pub struct AppConfig {
|
|||||||
pub oidc_button_text: String,
|
pub oidc_button_text: String,
|
||||||
/// Comma-separated list of OIDC group names that grant admin role.
|
/// Comma-separated list of OIDC group names that grant admin role.
|
||||||
pub oidc_admin_groups: String,
|
pub oidc_admin_groups: String,
|
||||||
|
/// Whether the Swagger UI is served at /swagger/.
|
||||||
|
pub swagger_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfig {
|
impl Default for AppConfig {
|
||||||
@@ -237,6 +241,7 @@ impl Default for AppConfig {
|
|||||||
auth_sso_enabled: false,
|
auth_sso_enabled: false,
|
||||||
oidc_button_text: "Sign in with SSO".into(),
|
oidc_button_text: "Sign in with SSO".into(),
|
||||||
oidc_admin_groups: String::new(),
|
oidc_admin_groups: String::new(),
|
||||||
|
swagger_enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +257,7 @@ impl_env_overrides!(
|
|||||||
auth_sso_enabled,
|
auth_sso_enabled,
|
||||||
oidc_button_text,
|
oidc_button_text,
|
||||||
oidc_admin_groups,
|
oidc_admin_groups,
|
||||||
|
swagger_enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
@@ -317,6 +323,7 @@ impl AppConfig {
|
|||||||
apply_db_field!(auth_sso_enabled);
|
apply_db_field!(auth_sso_enabled);
|
||||||
apply_db_field!(oidc_button_text);
|
apply_db_field!(oidc_button_text);
|
||||||
apply_db_field!(oidc_admin_groups);
|
apply_db_field!(oidc_admin_groups);
|
||||||
|
apply_db_field!(swagger_enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,11 @@ translations! {
|
|||||||
users_password_hint: "Leave blank to keep current" , "Оставьте пустым, чтобы не менять";
|
users_password_hint: "Leave blank to keep current" , "Оставьте пустым, чтобы не менять";
|
||||||
users_saved: "User saved." , "Пользователь сохранён.";
|
users_saved: "User saved." , "Пользователь сохранён.";
|
||||||
|
|
||||||
|
// API settings
|
||||||
|
settings_api: "API" , "API";
|
||||||
|
settings_swagger: "Swagger UI" , "Swagger UI";
|
||||||
|
settings_swagger_help: "Serves interactive API docs at /swagger/ (requires restart)" , "Интерактивная документация API на /swagger/ (требуется перезапуск)";
|
||||||
|
|
||||||
// OIDC login errors
|
// OIDC login errors
|
||||||
login_oidc_error: "SSO login failed. Please try again." , "Ошибка входа через SSO. Попробуйте ещё раз.";
|
login_oidc_error: "SSO login failed. Please try again." , "Ошибка входа через SSO. Попробуйте ещё раз.";
|
||||||
login_sso_disabled: "SSO login is not configured." , "Вход через SSO не настроен.";
|
login_sso_disabled: "SSO login is not configured." , "Вход через SSO не настроен.";
|
||||||
|
|||||||
+16
@@ -19,6 +19,7 @@ use cot::db::Database;
|
|||||||
use cot::form::{Form, FormResult};
|
use cot::form::{Form, FormResult};
|
||||||
use cot::html::Html;
|
use cot::html::Html;
|
||||||
use cot::middleware::SessionMiddleware;
|
use cot::middleware::SessionMiddleware;
|
||||||
|
use cot::static_files::StaticFilesMiddleware;
|
||||||
use cot::project::RegisterAppsContext;
|
use cot::project::RegisterAppsContext;
|
||||||
use cot::request::extractors::{RequestForm, UrlQuery};
|
use cot::request::extractors::{RequestForm, UrlQuery};
|
||||||
use cot::response::IntoResponse;
|
use cot::response::IntoResponse;
|
||||||
@@ -148,6 +149,11 @@ impl App for FuruApp {
|
|||||||
get(|| async { Ok::<_, cot::Error>(auth::redirect("/admin/")) }),
|
get(|| async { Ok::<_, cot::Error>(auth::redirect("/admin/")) }),
|
||||||
"admin_redirect",
|
"admin_redirect",
|
||||||
),
|
),
|
||||||
|
Route::with_handler_and_name(
|
||||||
|
"/swagger",
|
||||||
|
get(|| async { Ok::<_, cot::Error>(auth::redirect("/swagger/")) }),
|
||||||
|
"swagger_redirect",
|
||||||
|
),
|
||||||
Route::with_handler_and_name("/", index, "index"),
|
Route::with_handler_and_name("/", index, "index"),
|
||||||
Route::with_handler_and_name(
|
Route::with_handler_and_name(
|
||||||
"/login",
|
"/login",
|
||||||
@@ -258,6 +264,9 @@ impl Project for FuruProject {
|
|||||||
" FURU_OIDC_BUTTON_TEXT SSO button label (default: Sign in with SSO)\n",
|
" FURU_OIDC_BUTTON_TEXT SSO button label (default: Sign in with SSO)\n",
|
||||||
" FURU_OIDC_ADMIN_GROUPS OIDC groups that grant admin role\n",
|
" FURU_OIDC_ADMIN_GROUPS OIDC groups that grant admin role\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" API:\n",
|
||||||
|
" FURU_SWAGGER_ENABLED Enable Swagger UI at /swagger/ (default: false)\n",
|
||||||
|
"\n",
|
||||||
"QUICK START\n",
|
"QUICK START\n",
|
||||||
" export FURU_DATABASE_URL=postgres://user:pass@localhost/furumusic\n",
|
" export FURU_DATABASE_URL=postgres://user:pass@localhost/furumusic\n",
|
||||||
" furumusic run",
|
" furumusic run",
|
||||||
@@ -300,6 +309,7 @@ impl Project for FuruProject {
|
|||||||
context: &cot::project::MiddlewareContext,
|
context: &cot::project::MiddlewareContext,
|
||||||
) -> cot::project::RootHandler {
|
) -> cot::project::RootHandler {
|
||||||
handler
|
handler
|
||||||
|
.middleware(StaticFilesMiddleware::from_context(context))
|
||||||
.middleware(
|
.middleware(
|
||||||
SessionMiddleware::from_context(context)
|
SessionMiddleware::from_context(context)
|
||||||
.same_site(cot::config::SameSite::Lax),
|
.same_site(cot::config::SameSite::Lax),
|
||||||
@@ -320,6 +330,12 @@ impl Project for FuruProject {
|
|||||||
"/admin",
|
"/admin",
|
||||||
);
|
);
|
||||||
apps.register_with_views(api::ApiApp, "/api");
|
apps.register_with_views(api::ApiApp, "/api");
|
||||||
|
if self.app_config.swagger_enabled {
|
||||||
|
apps.register_with_views(
|
||||||
|
cot::openapi::swagger_ui::SwaggerUi::new(),
|
||||||
|
"/swagger",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,20 @@
|
|||||||
<td><span class="badge badge-{{ oidc_admin_groups_source }}">{{ oidc_admin_groups_source }}</span></td>
|
<td><span class="badge badge-{{ oidc_admin_groups_source }}">{{ oidc_admin_groups_source }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<h2>{{ t.settings_api }}</h2>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>{{ t.debug_field }}</th>
|
||||||
|
<th>{{ t.debug_value }}</th>
|
||||||
|
<th>{{ t.debug_source }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="swagger_enabled">{{ t.settings_swagger }}</label><br><span style="font-size:.75rem;color:#999;">{{ t.settings_swagger_help }}</span></td>
|
||||||
|
<td><input type="checkbox" name="swagger_enabled" id="swagger_enabled" value="on"{% if swagger_enabled %} checked{% endif %}></td>
|
||||||
|
<td><span class="badge badge-{{ swagger_enabled_source }}">{{ swagger_enabled_source }}</span></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<button type="submit" style="margin-top: 1rem; padding: .5rem 1.5rem;">{{ t.settings_save }}</button>
|
<button type="submit" style="margin-top: 1rem; padding: .5rem 1.5rem;">{{ t.settings_save }}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
Reference in New Issue
Block a user