You've already forked AstralRinth
forked from didirus/AstralRinth
Add utoipa Swagger UI support (#4602)
* Add utoipa Swagger UI support * remove unused code * remove unused code * consistency with trailing slash
This commit is contained in:
112
Cargo.lock
generated
112
Cargo.lock
generated
@@ -478,6 +478,7 @@ dependencies = [
|
|||||||
"serde_cbor",
|
"serde_cbor",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
|
"utoipa",
|
||||||
"uuid 1.18.1",
|
"uuid 1.18.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4678,6 +4679,9 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
"utoipa",
|
||||||
|
"utoipa-actix-web",
|
||||||
|
"utoipa-swagger-ui",
|
||||||
"uuid 1.18.1",
|
"uuid 1.18.1",
|
||||||
"validator",
|
"validator",
|
||||||
"webp",
|
"webp",
|
||||||
@@ -7335,6 +7339,40 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "8.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb44e1917075637ee8c7bcb865cf8830e3a92b5b1189e44e3a0ab5a0d5be314b"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "8.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "382499b49db77a7c19abd2a574f85ada7e9dbe125d5d1160fa5cad7c4cf71fc9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn 2.0.106",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "8.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21fcbee55c2458836bcdbfffb6ec9ba74bbc23ca7aa6816015a3dd2c4d8fc185"
|
||||||
|
dependencies = [
|
||||||
|
"sha2",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-ini"
|
name = "rust-ini"
|
||||||
version = "0.21.3"
|
version = "0.21.3"
|
||||||
@@ -10309,6 +10347,66 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa"
|
||||||
|
version = "5.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.11.4",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"utoipa-gen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-actix-web"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7eda9c23c05af0fb812f6a177514047331dac4851a2c8e9c4b895d6d826967f"
|
||||||
|
dependencies = [
|
||||||
|
"actix-service",
|
||||||
|
"actix-web",
|
||||||
|
"utoipa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-gen"
|
||||||
|
version = "5.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-swagger-ui"
|
||||||
|
version = "9.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
|
||||||
|
dependencies = [
|
||||||
|
"actix-web",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"mime_guess",
|
||||||
|
"regex",
|
||||||
|
"rust-embed",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
"utoipa",
|
||||||
|
"utoipa-swagger-ui-vendored",
|
||||||
|
"zip 3.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-swagger-ui-vendored"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@@ -11687,6 +11785,20 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"crc32fast",
|
||||||
|
"flate2",
|
||||||
|
"indexmap 2.11.4",
|
||||||
|
"memchr",
|
||||||
|
"zopfli",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "4.6.1"
|
version = "4.6.1"
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ tracing-subscriber = "0.3.20"
|
|||||||
typed-path = "0.12.0"
|
typed-path = "0.12.0"
|
||||||
url = "2.5.7"
|
url = "2.5.7"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
|
utoipa = { version = "5.4.0", features = ["actix_extras", "chrono", "decimal"] }
|
||||||
|
utoipa-actix-web = { version = "0.1.2" }
|
||||||
|
utoipa-swagger-ui = { version = "9.0.2", features = ["actix-web", "vendored"] }
|
||||||
uuid = "1.18.1"
|
uuid = "1.18.1"
|
||||||
validator = "0.20.0"
|
validator = "0.20.0"
|
||||||
webp = { version = "0.3.1", default-features = false }
|
webp = { version = "0.3.1", default-features = false }
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ tracing-ecs = { workspace = true }
|
|||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
|
utoipa = { workspace = true }
|
||||||
|
utoipa-actix-web = { workspace = true }
|
||||||
|
utoipa-swagger-ui = { workspace = true }
|
||||||
uuid = { workspace = true, features = ["fast-rng", "serde", "v4"] }
|
uuid = { workspace = true, features = ["fast-rng", "serde", "v4"] }
|
||||||
validator = { workspace = true, features = ["derive"] }
|
validator = { workspace = true, features = ["derive"] }
|
||||||
webp = { workspace = true }
|
webp = { workspace = true }
|
||||||
|
|||||||
@@ -345,6 +345,13 @@ pub fn app_config(
|
|||||||
.default_service(web::get().wrap(default_cors()).to(routes::not_found));
|
.default_service(web::get().wrap(default_cors()).to(routes::not_found));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn utoipa_app_config(
|
||||||
|
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||||
|
_labrinth_config: LabrinthConfig,
|
||||||
|
) {
|
||||||
|
cfg.configure(routes::v3::utoipa_config);
|
||||||
|
}
|
||||||
|
|
||||||
// This is so that env vars not used immediately don't panic at runtime
|
// This is so that env vars not used immediately don't panic at runtime
|
||||||
pub fn check_env_vars() -> bool {
|
pub fn check_env_vars() -> bool {
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use labrinth::util::anrok;
|
|||||||
use labrinth::util::env::parse_var;
|
use labrinth::util::env::parse_var;
|
||||||
use labrinth::util::gotenberg::GotenbergClient;
|
use labrinth::util::gotenberg::GotenbergClient;
|
||||||
use labrinth::util::ratelimit::rate_limit_middleware;
|
use labrinth::util::ratelimit::rate_limit_middleware;
|
||||||
|
use labrinth::utoipa_app_config;
|
||||||
use labrinth::{check_env_vars, clickhouse, database, file_hosting};
|
use labrinth::{check_env_vars, clickhouse, database, file_hosting};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -25,6 +26,9 @@ use tracing_ecs::ECSLayerBuilder;
|
|||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
use utoipa_actix_web::AppExt;
|
||||||
|
use utoipa_swagger_ui::SwaggerUi;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
@@ -293,6 +297,12 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.wrap(from_fn(rate_limit_middleware))
|
.wrap(from_fn(rate_limit_middleware))
|
||||||
.wrap(actix_web::middleware::Compress::default())
|
.wrap(actix_web::middleware::Compress::default())
|
||||||
.wrap(sentry_actix::Sentry::new())
|
.wrap(sentry_actix::Sentry::new())
|
||||||
|
.into_utoipa_app()
|
||||||
|
.configure(|cfg| utoipa_app_config(cfg, labrinth_config.clone()))
|
||||||
|
.openapi_service(|api| SwaggerUi::new("/docs/swagger-ui/{_:.*}")
|
||||||
|
.config(utoipa_swagger_ui::Config::default().try_it_out_enabled(true))
|
||||||
|
.url("/docs/openapi.json", ApiDoc::openapi().merge_from(api)))
|
||||||
|
.into_app()
|
||||||
.configure(|cfg| app_config(cfg, labrinth_config.clone()))
|
.configure(|cfg| app_config(cfg, labrinth_config.clone()))
|
||||||
})
|
})
|
||||||
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
.bind(dotenvy::var("BIND_ADDR").unwrap())?
|
||||||
@@ -300,6 +310,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(utoipa::OpenApi)]
|
||||||
|
#[openapi(info(title = "Labrinth"))]
|
||||||
|
struct ApiDoc;
|
||||||
|
|
||||||
fn log_error(err: &actix_web::Error) {
|
fn log_error(err: &actix_web::Error) {
|
||||||
if err.as_response_error().status_code().is_client_error() {
|
if err.as_response_error().status_code().is_client_error() {
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
|||||||
@@ -77,12 +77,8 @@ pub fn root_config(cfg: &mut web::ServiceConfig) {
|
|||||||
}.boxed_local()
|
}.boxed_local()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
cfg.service(
|
cfg.service(index::index_get);
|
||||||
web::scope("")
|
cfg.service(Files::new("/", "assets/"));
|
||||||
.wrap(default_cors())
|
|
||||||
.service(index::index_get)
|
|
||||||
.service(Files::new("/", "assets/")),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
//! requests, you have to zip together M arrays of N elements
|
//! requests, you have to zip together M arrays of N elements
|
||||||
//! - this makes it inconvenient to have separate endpoints
|
//! - this makes it inconvenient to have separate endpoints
|
||||||
|
|
||||||
|
mod old;
|
||||||
|
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
use actix_web::{HttpRequest, web};
|
use actix_web::{HttpRequest, post, web};
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use rust_decimal::Decimal;
|
use rust_decimal::Decimal;
|
||||||
@@ -32,10 +34,9 @@ use crate::{
|
|||||||
routes::ApiError,
|
routes::ApiError,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: this service `analytics` is shadowed by `analytics_get_old`'s
|
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||||
// see the TODO in `analytics_get_old.rs`
|
cfg.service(fetch_analytics);
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
cfg.configure(old::config);
|
||||||
cfg.service(web::scope("analytics").route("", web::post().to(get)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// request
|
// request
|
||||||
@@ -43,7 +44,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
/// Requests analytics data, aggregating over all possible analytics sources
|
/// Requests analytics data, aggregating over all possible analytics sources
|
||||||
/// like projects and affiliate codes, returning the data in a list of time
|
/// like projects and affiliate codes, returning the data in a list of time
|
||||||
/// slices.
|
/// slices.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct GetRequest {
|
pub struct GetRequest {
|
||||||
/// What time range to return statistics for.
|
/// What time range to return statistics for.
|
||||||
pub time_range: TimeRange,
|
pub time_range: TimeRange,
|
||||||
@@ -52,7 +53,7 @@ pub struct GetRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Time range for fetching analytics.
|
/// Time range for fetching analytics.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct TimeRange {
|
pub struct TimeRange {
|
||||||
/// When to start including data.
|
/// When to start including data.
|
||||||
pub start: DateTime<Utc>,
|
pub start: DateTime<Utc>,
|
||||||
@@ -68,20 +69,22 @@ pub struct TimeRange {
|
|||||||
|
|
||||||
/// Determines how many time slices between the start and end will be
|
/// Determines how many time slices between the start and end will be
|
||||||
/// included, and how fine-grained those time slices will be.
|
/// included, and how fine-grained those time slices will be.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum TimeRangeResolution {
|
pub enum TimeRangeResolution {
|
||||||
/// Use a set number of time slices, with the resolution being determined
|
/// Use a set number of time slices, with the resolution being determined
|
||||||
/// automatically.
|
/// automatically.
|
||||||
|
#[schema(value_type = u64)]
|
||||||
Slices(NonZeroU64),
|
Slices(NonZeroU64),
|
||||||
/// Each time slice will be a set number of minutes long, and the number of
|
/// Each time slice will be a set number of minutes long, and the number of
|
||||||
/// slices is determined automatically.
|
/// slices is determined automatically.
|
||||||
|
#[schema(value_type = u64)]
|
||||||
Minutes(NonZeroU64),
|
Minutes(NonZeroU64),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What metrics the caller would like to receive from this analytics get
|
/// What metrics the caller would like to receive from this analytics get
|
||||||
/// request.
|
/// request.
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ReturnMetrics {
|
pub struct ReturnMetrics {
|
||||||
/// How many times a project page has been viewed.
|
/// How many times a project page has been viewed.
|
||||||
pub project_views: Option<Metrics<ProjectViewsField>>,
|
pub project_views: Option<Metrics<ProjectViewsField>>,
|
||||||
@@ -90,11 +93,15 @@ pub struct ReturnMetrics {
|
|||||||
/// How long users have been playing a project.
|
/// How long users have been playing a project.
|
||||||
pub project_playtime: Option<Metrics<ProjectPlaytimeField>>,
|
pub project_playtime: Option<Metrics<ProjectPlaytimeField>>,
|
||||||
/// How much payout revenue a project has generated.
|
/// How much payout revenue a project has generated.
|
||||||
pub project_revenue: Option<Metrics<()>>,
|
pub project_revenue: Option<Metrics<Unit>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replacement for `()` because of a `utoipa` limitation.
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
|
pub struct Unit {}
|
||||||
|
|
||||||
/// See [`ReturnMetrics`].
|
/// See [`ReturnMetrics`].
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct Metrics<F> {
|
pub struct Metrics<F> {
|
||||||
/// When collecting metrics, what fields do we want to group the results by?
|
/// When collecting metrics, what fields do we want to group the results by?
|
||||||
///
|
///
|
||||||
@@ -114,7 +121,9 @@ pub struct Metrics<F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fields for [`ReturnMetrics::project_views`].
|
/// Fields for [`ReturnMetrics::project_views`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ProjectViewsField {
|
pub enum ProjectViewsField {
|
||||||
/// Project ID.
|
/// Project ID.
|
||||||
@@ -132,7 +141,9 @@ pub enum ProjectViewsField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fields for [`ReturnMetrics::project_downloads`].
|
/// Fields for [`ReturnMetrics::project_downloads`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ProjectDownloadsField {
|
pub enum ProjectDownloadsField {
|
||||||
/// Project ID.
|
/// Project ID.
|
||||||
@@ -150,7 +161,9 @@ pub enum ProjectDownloadsField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Fields for [`ReturnMetrics::project_playtime`].
|
/// Fields for [`ReturnMetrics::project_playtime`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema,
|
||||||
|
)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ProjectPlaytimeField {
|
pub enum ProjectPlaytimeField {
|
||||||
/// Project ID.
|
/// Project ID.
|
||||||
@@ -177,15 +190,15 @@ pub const MAX_TIME_SLICES: usize = 1024;
|
|||||||
/// This is a list of N [`TimeSlice`]s, where each slice represents an equal
|
/// This is a list of N [`TimeSlice`]s, where each slice represents an equal
|
||||||
/// time interval of metrics collection. The number of slices is determined
|
/// time interval of metrics collection. The number of slices is determined
|
||||||
/// by [`GetRequest::time_range`].
|
/// by [`GetRequest::time_range`].
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct GetResponse(pub Vec<TimeSlice>);
|
pub struct FetchResponse(pub Vec<TimeSlice>);
|
||||||
|
|
||||||
/// Single time interval of metrics collection.
|
/// Single time interval of metrics collection.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct TimeSlice(pub Vec<AnalyticsData>);
|
pub struct TimeSlice(pub Vec<AnalyticsData>);
|
||||||
|
|
||||||
/// Metrics collected in a single [`TimeSlice`].
|
/// Metrics collected in a single [`TimeSlice`].
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
#[serde(untagged)] // the presence of `source_project`, `source_affiliate_code` determines the kind
|
#[serde(untagged)] // the presence of `source_project`, `source_affiliate_code` determines the kind
|
||||||
pub enum AnalyticsData {
|
pub enum AnalyticsData {
|
||||||
/// Project metrics.
|
/// Project metrics.
|
||||||
@@ -194,7 +207,7 @@ pub enum AnalyticsData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Project metrics.
|
/// Project metrics.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ProjectAnalytics {
|
pub struct ProjectAnalytics {
|
||||||
/// What project these metrics are for.
|
/// What project these metrics are for.
|
||||||
source_project: ProjectId,
|
source_project: ProjectId,
|
||||||
@@ -213,7 +226,7 @@ impl ProjectAnalytics {
|
|||||||
/// Project metrics of a specific kind.
|
/// Project metrics of a specific kind.
|
||||||
///
|
///
|
||||||
/// If a field is not included in [`Metrics::bucket_by`], it will be [`None`].
|
/// If a field is not included in [`Metrics::bucket_by`], it will be [`None`].
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
#[serde(rename_all = "snake_case", tag = "metric_kind")]
|
#[serde(rename_all = "snake_case", tag = "metric_kind")]
|
||||||
pub enum ProjectMetrics {
|
pub enum ProjectMetrics {
|
||||||
/// [`ReturnMetrics::project_views`].
|
/// [`ReturnMetrics::project_views`].
|
||||||
@@ -227,7 +240,7 @@ pub enum ProjectMetrics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// [`ReturnMetrics::project_views`].
|
/// [`ReturnMetrics::project_views`].
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ProjectViews {
|
pub struct ProjectViews {
|
||||||
/// [`ProjectViewsField::Domain`].
|
/// [`ProjectViewsField::Domain`].
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -246,7 +259,7 @@ pub struct ProjectViews {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// [`ReturnMetrics::project_downloads`].
|
/// [`ReturnMetrics::project_downloads`].
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ProjectDownloads {
|
pub struct ProjectDownloads {
|
||||||
/// [`ProjectDownloadsField::Domain`].
|
/// [`ProjectDownloadsField::Domain`].
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -265,7 +278,7 @@ pub struct ProjectDownloads {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// [`ReturnMetrics::project_playtime`].
|
/// [`ReturnMetrics::project_playtime`].
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ProjectPlaytime {
|
pub struct ProjectPlaytime {
|
||||||
/// [`ProjectPlaytimeField::VersionId`].
|
/// [`ProjectPlaytimeField::VersionId`].
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -281,7 +294,7 @@ pub struct ProjectPlaytime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// [`ReturnMetrics::project_revenue`].
|
/// [`ReturnMetrics::project_revenue`].
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, utoipa::ToSchema)]
|
||||||
pub struct ProjectRevenue {
|
pub struct ProjectRevenue {
|
||||||
/// Total revenue for this bucket.
|
/// Total revenue for this bucket.
|
||||||
revenue: Decimal,
|
revenue: Decimal,
|
||||||
@@ -414,14 +427,19 @@ mod query {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(
|
/// Fetches analytics data for the authorized user's projects.
|
||||||
|
#[utoipa::path(
|
||||||
|
responses((status = OK, body = inline(FetchResponse))),
|
||||||
|
)]
|
||||||
|
#[post("")]
|
||||||
|
pub async fn fetch_analytics(
|
||||||
http_req: HttpRequest,
|
http_req: HttpRequest,
|
||||||
req: web::Json<GetRequest>,
|
req: web::Json<GetRequest>,
|
||||||
pool: web::Data<PgPool>,
|
pool: web::Data<PgPool>,
|
||||||
redis: web::Data<RedisPool>,
|
redis: web::Data<RedisPool>,
|
||||||
session_queue: web::Data<AuthQueue>,
|
session_queue: web::Data<AuthQueue>,
|
||||||
clickhouse: web::Data<clickhouse::Client>,
|
clickhouse: web::Data<clickhouse::Client>,
|
||||||
) -> Result<web::Json<GetResponse>, ApiError> {
|
) -> Result<web::Json<FetchResponse>, ApiError> {
|
||||||
let (scopes, user) = get_user_from_headers(
|
let (scopes, user) = get_user_from_headers(
|
||||||
&http_req,
|
&http_req,
|
||||||
&**pool,
|
&**pool,
|
||||||
@@ -655,7 +673,7 @@ pub async fn get(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(web::Json(GetResponse(time_slices)))
|
Ok(web::Json(FetchResponse(time_slices)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn none_if_empty(s: String) -> Option<String> {
|
fn none_if_empty(s: String) -> Option<String> {
|
||||||
@@ -824,7 +842,7 @@ mod tests {
|
|||||||
let test_project_2 = ProjectId(456);
|
let test_project_2 = ProjectId(456);
|
||||||
let test_project_3 = ProjectId(789);
|
let test_project_3 = ProjectId(789);
|
||||||
|
|
||||||
let src = GetResponse(vec![
|
let src = FetchResponse(vec![
|
||||||
TimeSlice(vec![
|
TimeSlice(vec![
|
||||||
AnalyticsData::Project(ProjectAnalytics {
|
AnalyticsData::Project(ProjectAnalytics {
|
||||||
source_project: test_project_1,
|
source_project: test_project_1,
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ use crate::models::teams::ProjectPermissions;
|
|||||||
use crate::{
|
use crate::{
|
||||||
auth::get_user_from_headers,
|
auth::get_user_from_headers,
|
||||||
database::models::user_item,
|
database::models::user_item,
|
||||||
models::{
|
models::{ids::ProjectId, pats::Scopes},
|
||||||
ids::{ProjectId, VersionId},
|
|
||||||
pats::Scopes,
|
|
||||||
},
|
|
||||||
queue::session::AuthQueue,
|
queue::session::AuthQueue,
|
||||||
};
|
};
|
||||||
use actix_web::{HttpRequest, HttpResponse, web};
|
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||||
use ariadne::ids::base62_impl::to_base62;
|
use ariadne::ids::base62_impl::to_base62;
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use eyre::eyre;
|
use eyre::eyre;
|
||||||
@@ -24,28 +21,21 @@ use std::collections::HashMap;
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||||
cfg.service(
|
cfg.service(playtimes_get)
|
||||||
web::scope("analytics")
|
.service(views_get)
|
||||||
// TODO: since our service shadows analytics v2, we have to redirect here
|
.service(downloads_get)
|
||||||
.route("", web::post().to(super::analytics_get::get))
|
.service(revenue_get)
|
||||||
.route("playtime", web::get().to(playtimes_get))
|
.service(countries_downloads_get)
|
||||||
.route("views", web::get().to(views_get))
|
.service(countries_views_get);
|
||||||
.route("downloads", web::get().to(downloads_get))
|
|
||||||
.route("revenue", web::get().to(revenue_get))
|
|
||||||
.route(
|
|
||||||
"countries/downloads",
|
|
||||||
web::get().to(countries_downloads_get),
|
|
||||||
)
|
|
||||||
.route("countries/views", web::get().to(countries_views_get)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The json data to be passed to fetch analytic data
|
/// The json data to be passed to fetch analytic data.
|
||||||
|
///
|
||||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||||
/// start_date and end_date are optional, and default to two weeks ago, and the maximum date respectively.
|
/// start_date and end_date are optional, and default to two weeks ago, and the maximum date respectively.
|
||||||
/// resolution_minutes is optional. This refers to the window by which we are looking (every day, every minute, etc) and defaults to 1440 (1 day)
|
/// resolution_minutes is optional. This refers to the window by which we are looking (every day, every minute, etc) and defaults to 1440 (1 day)
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)]
|
||||||
pub struct GetData {
|
pub struct GetData {
|
||||||
// only one of project_ids or version_ids should be used
|
// only one of project_ids or version_ids should be used
|
||||||
// if neither are provided, all projects the user has access to will be used
|
// if neither are provided, all projects the user has access to will be used
|
||||||
@@ -54,26 +44,12 @@ pub struct GetData {
|
|||||||
pub start_date: Option<DateTime<Utc>>, // defaults to 2 weeks ago
|
pub start_date: Option<DateTime<Utc>>, // defaults to 2 weeks ago
|
||||||
pub end_date: Option<DateTime<Utc>>, // defaults to now
|
pub end_date: Option<DateTime<Utc>>, // defaults to now
|
||||||
|
|
||||||
|
#[schema(value_type = Option<u32>, minimum = 1)]
|
||||||
pub resolution_minutes: Option<NonZeroU32>, // defaults to 1 day. Ignored in routes that do not aggregate over a resolution (eg: /countries)
|
pub resolution_minutes: Option<NonZeroU32>, // defaults to 1 day. Ignored in routes that do not aggregate over a resolution (eg: /countries)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get playtime data for a set of projects or versions
|
#[utoipa::path]
|
||||||
/// Data is returned as a hashmap of project/version ids to a hashmap of days to playtime data
|
#[get("/playtime")]
|
||||||
/// eg:
|
|
||||||
/// {
|
|
||||||
/// "4N1tEhnO": {
|
|
||||||
/// "20230824": 23
|
|
||||||
/// }
|
|
||||||
///}
|
|
||||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct FetchedPlaytime {
|
|
||||||
pub time: u64,
|
|
||||||
pub total_seconds: u64,
|
|
||||||
pub loader_seconds: HashMap<String, u64>,
|
|
||||||
pub game_version_seconds: HashMap<String, u64>,
|
|
||||||
pub parent_seconds: HashMap<VersionId, u64>,
|
|
||||||
}
|
|
||||||
pub async fn playtimes_get(
|
pub async fn playtimes_get(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
clickhouse: web::Data<clickhouse::Client>,
|
clickhouse: web::Data<clickhouse::Client>,
|
||||||
@@ -134,7 +110,8 @@ pub async fn playtimes_get(
|
|||||||
Ok(HttpResponse::Ok().json(hm))
|
Ok(HttpResponse::Ok().json(hm))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get view data for a set of projects or versions
|
/// Get view data for a set of projects or versions.
|
||||||
|
///
|
||||||
/// Data is returned as a hashmap of project/version ids to a hashmap of days to views
|
/// Data is returned as a hashmap of project/version ids to a hashmap of days to views
|
||||||
/// eg:
|
/// eg:
|
||||||
/// {
|
/// {
|
||||||
@@ -143,6 +120,8 @@ pub async fn playtimes_get(
|
|||||||
/// }
|
/// }
|
||||||
///}
|
///}
|
||||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||||
|
#[utoipa::path]
|
||||||
|
#[get("/views")]
|
||||||
pub async fn views_get(
|
pub async fn views_get(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
clickhouse: web::Data<clickhouse::Client>,
|
clickhouse: web::Data<clickhouse::Client>,
|
||||||
@@ -203,7 +182,8 @@ pub async fn views_get(
|
|||||||
Ok(HttpResponse::Ok().json(hm))
|
Ok(HttpResponse::Ok().json(hm))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get download data for a set of projects or versions
|
/// Get download data for a set of projects or versions.
|
||||||
|
///
|
||||||
/// Data is returned as a hashmap of project/version ids to a hashmap of days to downloads
|
/// Data is returned as a hashmap of project/version ids to a hashmap of days to downloads
|
||||||
/// eg:
|
/// eg:
|
||||||
/// {
|
/// {
|
||||||
@@ -212,6 +192,8 @@ pub async fn views_get(
|
|||||||
/// }
|
/// }
|
||||||
///}
|
///}
|
||||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||||
|
#[utoipa::path]
|
||||||
|
#[get("/downloads")]
|
||||||
pub async fn downloads_get(
|
pub async fn downloads_get(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
clickhouse: web::Data<clickhouse::Client>,
|
clickhouse: web::Data<clickhouse::Client>,
|
||||||
@@ -273,7 +255,8 @@ pub async fn downloads_get(
|
|||||||
Ok(HttpResponse::Ok().json(hm))
|
Ok(HttpResponse::Ok().json(hm))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get payout data for a set of projects
|
/// Get payout data for a set of projects.
|
||||||
|
///
|
||||||
/// Data is returned as a hashmap of project ids to a hashmap of days to amount earned per day
|
/// Data is returned as a hashmap of project ids to a hashmap of days to amount earned per day
|
||||||
/// eg:
|
/// eg:
|
||||||
/// {
|
/// {
|
||||||
@@ -282,6 +265,8 @@ pub async fn downloads_get(
|
|||||||
/// }
|
/// }
|
||||||
///}
|
///}
|
||||||
/// ONLY project IDs can be used. Unauthorized projects will be filtered out.
|
/// ONLY project IDs can be used. Unauthorized projects will be filtered out.
|
||||||
|
#[utoipa::path]
|
||||||
|
#[get("/revenue")]
|
||||||
pub async fn revenue_get(
|
pub async fn revenue_get(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
data: web::Query<GetData>,
|
data: web::Query<GetData>,
|
||||||
@@ -409,7 +394,8 @@ pub async fn revenue_get(
|
|||||||
Ok(HttpResponse::Ok().json(hm))
|
Ok(HttpResponse::Ok().json(hm))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get country data for a set of projects or versions
|
/// Get country data for a set of projects or versions.
|
||||||
|
///
|
||||||
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to downloads.
|
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to downloads.
|
||||||
/// Unknown countries are labeled "".
|
/// Unknown countries are labeled "".
|
||||||
/// This is usable to see significant performing countries per project
|
/// This is usable to see significant performing countries per project
|
||||||
@@ -421,6 +407,8 @@ pub async fn revenue_get(
|
|||||||
///}
|
///}
|
||||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||||
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
|
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
|
||||||
|
#[utoipa::path]
|
||||||
|
#[get("/countries/downloads")]
|
||||||
pub async fn countries_downloads_get(
|
pub async fn countries_downloads_get(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
clickhouse: web::Data<clickhouse::Client>,
|
clickhouse: web::Data<clickhouse::Client>,
|
||||||
@@ -482,7 +470,8 @@ pub async fn countries_downloads_get(
|
|||||||
Ok(HttpResponse::Ok().json(hm))
|
Ok(HttpResponse::Ok().json(hm))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get country data for a set of projects or versions
|
/// Get country data for a set of projects or versions.
|
||||||
|
///
|
||||||
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to views.
|
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to views.
|
||||||
/// Unknown countries are labeled "".
|
/// Unknown countries are labeled "".
|
||||||
/// This is usable to see significant performing countries per project
|
/// This is usable to see significant performing countries per project
|
||||||
@@ -494,6 +483,8 @@ pub async fn countries_downloads_get(
|
|||||||
///}
|
///}
|
||||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||||
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
|
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
|
||||||
|
#[utoipa::path]
|
||||||
|
#[get("/countries/views")]
|
||||||
pub async fn countries_views_get(
|
pub async fn countries_views_get(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
clickhouse: web::Data<clickhouse::Client>,
|
clickhouse: web::Data<clickhouse::Client>,
|
||||||
@@ -4,7 +4,6 @@ use actix_web::{HttpResponse, web};
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
pub mod analytics_get;
|
pub mod analytics_get;
|
||||||
pub mod analytics_get_old;
|
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod friends;
|
pub mod friends;
|
||||||
pub mod images;
|
pub mod images;
|
||||||
@@ -33,8 +32,6 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
web::scope("v3")
|
web::scope("v3")
|
||||||
.wrap(default_cors())
|
.wrap(default_cors())
|
||||||
.configure(limits::config)
|
.configure(limits::config)
|
||||||
// .configure(analytics_get::config) // TODO: see `analytics_get`
|
|
||||||
.configure(analytics_get_old::config)
|
|
||||||
.configure(collections::config)
|
.configure(collections::config)
|
||||||
.configure(images::config)
|
.configure(images::config)
|
||||||
.configure(notifications::config)
|
.configure(notifications::config)
|
||||||
@@ -56,6 +53,15 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn utoipa_config(
|
||||||
|
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||||
|
) {
|
||||||
|
cfg.service(
|
||||||
|
utoipa_actix_web::scope("/v3/analytics")
|
||||||
|
.configure(analytics_get::config),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn hello_world() -> Result<HttpResponse, ApiError> {
|
pub async fn hello_world() -> Result<HttpResponse, ApiError> {
|
||||||
Ok(HttpResponse::Ok().json(json!({
|
Ok(HttpResponse::Ok().json(json!({
|
||||||
"hello": "world",
|
"hello": "world",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use actix_web::{App, dev::ServiceResponse, test};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use labrinth::LabrinthConfig;
|
use labrinth::LabrinthConfig;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use utoipa_actix_web::AppExt;
|
||||||
|
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod request_data;
|
pub mod request_data;
|
||||||
@@ -22,9 +23,15 @@ pub struct ApiV2 {
|
|||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl ApiBuildable for ApiV2 {
|
impl ApiBuildable for ApiV2 {
|
||||||
async fn build(labrinth_config: LabrinthConfig) -> Self {
|
async fn build(labrinth_config: LabrinthConfig) -> Self {
|
||||||
let app = App::new().configure(|cfg| {
|
let app = App::new()
|
||||||
labrinth::app_config(cfg, labrinth_config.clone())
|
.into_utoipa_app()
|
||||||
});
|
.configure(|cfg| {
|
||||||
|
labrinth::utoipa_app_config(cfg, labrinth_config.clone())
|
||||||
|
})
|
||||||
|
.into_app()
|
||||||
|
.configure(|cfg| {
|
||||||
|
labrinth::app_config(cfg, labrinth_config.clone())
|
||||||
|
});
|
||||||
let test_app: Rc<dyn LocalService> =
|
let test_app: Rc<dyn LocalService> =
|
||||||
Rc::new(test::init_service(app).await);
|
Rc::new(test::init_service(app).await);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use actix_web::{App, dev::ServiceResponse, test};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use labrinth::LabrinthConfig;
|
use labrinth::LabrinthConfig;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use utoipa_actix_web::AppExt;
|
||||||
|
|
||||||
pub mod collections;
|
pub mod collections;
|
||||||
pub mod limits;
|
pub mod limits;
|
||||||
@@ -27,9 +28,15 @@ pub struct ApiV3 {
|
|||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl ApiBuildable for ApiV3 {
|
impl ApiBuildable for ApiV3 {
|
||||||
async fn build(labrinth_config: LabrinthConfig) -> Self {
|
async fn build(labrinth_config: LabrinthConfig) -> Self {
|
||||||
let app = App::new().configure(|cfg| {
|
let app = App::new()
|
||||||
labrinth::app_config(cfg, labrinth_config.clone())
|
.into_utoipa_app()
|
||||||
});
|
.configure(|cfg| {
|
||||||
|
labrinth::utoipa_app_config(cfg, labrinth_config.clone())
|
||||||
|
})
|
||||||
|
.into_app()
|
||||||
|
.configure(|cfg| {
|
||||||
|
labrinth::app_config(cfg, labrinth_config.clone())
|
||||||
|
});
|
||||||
let test_app: Rc<dyn LocalService> =
|
let test_app: Rc<dyn LocalService> =
|
||||||
Rc::new(test::init_service(app).await);
|
Rc::new(test::init_service(app).await);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ serde_bytes = { workspace = true }
|
|||||||
serde_cbor = { workspace = true }
|
serde_cbor = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
utoipa = { workspace = true }
|
||||||
uuid = { workspace = true, features = ["fast-rng", "serde", "v4"] }
|
uuid = { workspace = true, features = ["fast-rng", "serde", "v4"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ macro_rules! base62_id {
|
|||||||
serde::Deserialize,
|
serde::Deserialize,
|
||||||
Debug,
|
Debug,
|
||||||
Hash,
|
Hash,
|
||||||
|
utoipa::ToSchema,
|
||||||
)]
|
)]
|
||||||
#[serde(from = "ariadne::ids::Base62Id")]
|
#[serde(from = "ariadne::ids::Base62Id")]
|
||||||
#[serde(into = "ariadne::ids::Base62Id")]
|
#[serde(into = "ariadne::ids::Base62Id")]
|
||||||
|
|||||||
Reference in New Issue
Block a user