Add utoipa info for v2 routes (#5775)
* wip: add v2 docs, routes to config, paths * fix up path prefixes * fix leading slashes * fix slash route * fix more slashes * wip: full utopification of v2 * convert last few v2 routes to utoipa
This commit is contained in:
@@ -353,7 +353,6 @@ pub fn app_config(
|
||||
.app_data(web::Data::new(labrinth_config.stripe_client.clone()))
|
||||
.app_data(web::Data::new(labrinth_config.anrok_client.clone()))
|
||||
.app_data(labrinth_config.rate_limiter.clone())
|
||||
.configure(routes::v2::config)
|
||||
.configure(routes::v3::config)
|
||||
.configure(routes::internal::config)
|
||||
.configure(routes::root_config)
|
||||
@@ -374,6 +373,7 @@ pub fn utoipa_app_config(
|
||||
|_cfg| ()
|
||||
}
|
||||
})
|
||||
.configure(routes::v2::utoipa_config)
|
||||
.configure(routes::v3::utoipa_config)
|
||||
.configure(routes::internal::utoipa_config);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// A project returned from the API
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct LegacyProject {
|
||||
/// Relevant V2 fields- these were removed or modified in V3,
|
||||
/// and are now part of the dynamic fields system
|
||||
@@ -253,7 +253,9 @@ impl LegacyProject {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Copy)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Copy, utoipa::ToSchema,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LegacySideType {
|
||||
Required,
|
||||
@@ -290,7 +292,7 @@ impl LegacySideType {
|
||||
}
|
||||
|
||||
/// A specific version of a project
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct LegacyVersion {
|
||||
/// Relevant V2 fields- these were removed or modified in V3,
|
||||
/// and are now part of the dynamic fields system
|
||||
@@ -368,7 +370,7 @@ impl From<Version> for LegacyVersion {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)]
|
||||
pub struct LegacyGalleryItem {
|
||||
pub url: String,
|
||||
pub raw_url: String,
|
||||
@@ -393,7 +395,9 @@ impl LegacyGalleryItem {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate, Clone, Eq, PartialEq)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Validate, Clone, Eq, PartialEq, utoipa::ToSchema,
|
||||
)]
|
||||
pub struct DonationLink {
|
||||
pub id: String,
|
||||
pub platform: String,
|
||||
|
||||
@@ -124,6 +124,19 @@ bitflags::bitflags! {
|
||||
|
||||
bitflags_serde_impl!(Scopes, u64);
|
||||
|
||||
impl utoipa::PartialSchema for Scopes {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
utoipa::openapi::ObjectBuilder::new()
|
||||
.schema_type(utoipa::openapi::schema::Type::Integer)
|
||||
.format(Some(utoipa::openapi::SchemaFormat::KnownFormat(
|
||||
utoipa::openapi::KnownFormat::Int64,
|
||||
)))
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for Scopes {}
|
||||
|
||||
impl Scopes {
|
||||
// these scopes cannot be specified in a personal access token
|
||||
pub fn restricted() -> Scopes {
|
||||
|
||||
@@ -33,6 +33,23 @@ bitflags::bitflags! {
|
||||
|
||||
bitflags_serde_impl!(ProjectPermissions, u64);
|
||||
|
||||
impl utoipa::PartialSchema for ProjectPermissions {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
u64::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for ProjectPermissions {
|
||||
fn schemas(
|
||||
schemas: &mut Vec<(
|
||||
String,
|
||||
utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>,
|
||||
)>,
|
||||
) {
|
||||
u64::schemas(schemas);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProjectPermissions {
|
||||
fn default() -> ProjectPermissions {
|
||||
ProjectPermissions::empty()
|
||||
@@ -92,6 +109,23 @@ bitflags::bitflags! {
|
||||
|
||||
bitflags_serde_impl!(OrganizationPermissions, u64);
|
||||
|
||||
impl utoipa::PartialSchema for OrganizationPermissions {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
u64::schema()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for OrganizationPermissions {
|
||||
fn schemas(
|
||||
schemas: &mut Vec<(
|
||||
String,
|
||||
utoipa::openapi::RefOr<utoipa::openapi::schema::Schema>,
|
||||
)>,
|
||||
) {
|
||||
u64::schemas(schemas);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OrganizationPermissions {
|
||||
fn default() -> OrganizationPermissions {
|
||||
OrganizationPermissions::NONE
|
||||
|
||||
@@ -17,15 +17,15 @@ use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("admin")
|
||||
utoipa_actix_web::scope("/admin")
|
||||
.service(count_download)
|
||||
.service(force_reindex),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct DownloadBody {
|
||||
pub url: String,
|
||||
pub project_id: ProjectId,
|
||||
@@ -36,6 +36,14 @@ pub struct DownloadBody {
|
||||
}
|
||||
|
||||
// This is an internal route, cannot be used without key
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "countDownload",
|
||||
responses(
|
||||
(status = 204, description = "Download counted successfully"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[patch("/_count-download", guard = "admin_key_guard")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn count_download(
|
||||
@@ -150,6 +158,14 @@ pub async fn count_download(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "forceReindex",
|
||||
responses(
|
||||
(status = 204, description = "Search index rebuilt successfully"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
)
|
||||
)]
|
||||
#[post("/_force_reindex", guard = "admin_key_guard")]
|
||||
pub async fn force_reindex(
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -44,7 +44,7 @@ use tracing::warn;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("billing")
|
||||
web::scope("/billing")
|
||||
.service(products)
|
||||
.service(subscriptions)
|
||||
.service(user_customer)
|
||||
|
||||
@@ -37,7 +37,7 @@ pub mod rescan;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("delphi")
|
||||
web::scope("/delphi")
|
||||
.service(ingest_report)
|
||||
.service(_run)
|
||||
.service(version)
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::util::error::Context;
|
||||
use crate::util::ext::get_image_ext;
|
||||
use crate::util::img::upload_image_optimized;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::web::{Data, Query, ServiceConfig, scope};
|
||||
use actix_web::web::{Data, Query};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use argon2::password_hash::SaltString;
|
||||
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
@@ -43,9 +43,9 @@ use tracing::info;
|
||||
use validator::Validate;
|
||||
use zxcvbn::Score;
|
||||
|
||||
pub fn config(cfg: &mut ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
scope("auth")
|
||||
utoipa_actix_web::scope("/auth")
|
||||
.service(init)
|
||||
.service(auth_callback)
|
||||
.service(delete_auth_provider)
|
||||
@@ -1041,7 +1041,7 @@ impl AuthProvider {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct AuthorizationInit {
|
||||
pub url: String,
|
||||
#[serde(default)]
|
||||
@@ -1051,7 +1051,7 @@ pub struct AuthorizationInit {
|
||||
/// this will be set to the user's auth token from the frontend.
|
||||
pub auth_token: Option<String>,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct Authorization {
|
||||
pub code: String,
|
||||
pub state: String,
|
||||
@@ -1059,7 +1059,15 @@ pub struct Authorization {
|
||||
|
||||
// Init link takes us to GitHub API and calls back to callback endpoint with a code and state
|
||||
// http://localhost:8000/auth/init?url=https://modrinth.com
|
||||
#[get("init")]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "authInit",
|
||||
responses(
|
||||
(status = 307, description = "Redirect to OAuth provider"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[get("/init")]
|
||||
pub async fn init(
|
||||
req: HttpRequest,
|
||||
Query(info): Query<AuthorizationInit>, // callback url
|
||||
@@ -1140,7 +1148,15 @@ pub async fn init(
|
||||
.json(serde_json::json!({ "url": url })))
|
||||
}
|
||||
|
||||
#[get("callback")]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "authCallback",
|
||||
responses(
|
||||
(status = 307, description = "Redirect with auth code"),
|
||||
(status = 401, description = "Authentication failed")
|
||||
)
|
||||
)]
|
||||
#[get("/callback")]
|
||||
pub async fn auth_callback(
|
||||
req: HttpRequest,
|
||||
Query(query): Query<HashMap<String, String>>,
|
||||
@@ -1336,12 +1352,22 @@ pub async fn auth_callback(
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct DeleteAuthProvider {
|
||||
pub provider: AuthProvider,
|
||||
}
|
||||
|
||||
#[delete("provider")]
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteAuthProvider",
|
||||
responses(
|
||||
(status = 204, description = "Auth provider removed"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["USER_AUTH_WRITE"]))
|
||||
)]
|
||||
#[delete("/provider")]
|
||||
pub async fn delete_auth_provider(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -1425,7 +1451,7 @@ pub async fn check_sendy_subscription(
|
||||
Ok(response.trim() == "Subscribed")
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct NewAccount {
|
||||
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
|
||||
pub username: String,
|
||||
@@ -1437,7 +1463,15 @@ pub struct NewAccount {
|
||||
pub sign_up_newsletter: Option<bool>,
|
||||
}
|
||||
|
||||
#[post("create")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "createAccountPassword",
|
||||
responses(
|
||||
(status = 200, description = "Account created"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[post("/create")]
|
||||
pub async fn create_account_with_password(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -1566,7 +1600,7 @@ pub async fn create_account_with_password(
|
||||
Ok(HttpResponse::Ok().json(res))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct Login {
|
||||
#[serde(rename = "username")]
|
||||
pub username_or_email: String,
|
||||
@@ -1574,7 +1608,15 @@ pub struct Login {
|
||||
pub challenge: String,
|
||||
}
|
||||
|
||||
#[post("login")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "loginPassword",
|
||||
responses(
|
||||
(status = 200, description = "Login successful"),
|
||||
(status = 401, description = "Invalid credentials")
|
||||
)
|
||||
)]
|
||||
#[post("/login")]
|
||||
pub async fn login_password(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -1639,7 +1681,7 @@ pub async fn login_password(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct Login2FA {
|
||||
pub code: String,
|
||||
pub flow: String,
|
||||
@@ -1724,7 +1766,15 @@ async fn validate_2fa_code(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("login/2fa")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "login2fa",
|
||||
responses(
|
||||
(status = 200, description = "2FA login successful"),
|
||||
(status = 401, description = "Invalid credentials")
|
||||
)
|
||||
)]
|
||||
#[post("/login/2fa")]
|
||||
pub async fn login_2fa(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -1773,7 +1823,16 @@ pub async fn login_2fa(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("2fa/get_secret")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "begin2faFlow",
|
||||
responses(
|
||||
(status = 200, description = "2FA secret generated"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[post("/2fa/get_secret")]
|
||||
pub async fn begin_2fa_flow(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -1812,7 +1871,16 @@ pub async fn begin_2fa_flow(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("2fa")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "finish2faFlow",
|
||||
responses(
|
||||
(status = 200, description = "2FA enabled"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[post("/2fa")]
|
||||
pub async fn finish_2fa_flow(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -1930,12 +1998,21 @@ pub async fn finish_2fa_flow(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct Remove2FA {
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[delete("2fa")]
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "remove2fa",
|
||||
responses(
|
||||
(status = 204, description = "2FA removed"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[delete("/2fa")]
|
||||
pub async fn remove_2fa(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -2016,14 +2093,22 @@ pub async fn remove_2fa(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ResetPassword {
|
||||
#[serde(rename = "username")]
|
||||
pub username_or_email: String,
|
||||
pub challenge: String,
|
||||
}
|
||||
|
||||
#[post("password/reset")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "resetPasswordBegin",
|
||||
responses(
|
||||
(status = 204, description = "Password reset email sent"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[post("/password/reset")]
|
||||
pub async fn reset_password_begin(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -2111,14 +2196,24 @@ pub async fn reset_password_begin(
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct ChangePassword {
|
||||
pub flow: Option<String>,
|
||||
pub old_password: Option<String>,
|
||||
pub new_password: Option<String>,
|
||||
}
|
||||
|
||||
#[patch("password")]
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "changePassword",
|
||||
responses(
|
||||
(status = 204, description = "Password changed"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[patch("/password")]
|
||||
pub async fn change_password(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -2265,13 +2360,23 @@ pub async fn change_password(
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct SetEmail {
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[patch("email")]
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "setEmail",
|
||||
responses(
|
||||
(status = 204, description = "Email set"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[patch("/email")]
|
||||
pub async fn set_email(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -2380,7 +2485,16 @@ pub async fn set_email(
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[post("email/resend_verify")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "resendVerifyEmail",
|
||||
responses(
|
||||
(status = 204, description = "Verification email resent"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[post("/email/resend_verify")]
|
||||
pub async fn resend_verify_email(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -2438,12 +2552,20 @@ pub async fn resend_verify_email(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct VerifyEmail {
|
||||
pub flow: String,
|
||||
}
|
||||
|
||||
#[post("email/verify")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "verifyEmail",
|
||||
responses(
|
||||
(status = 204, description = "Email verified"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[post("/email/verify")]
|
||||
pub async fn verify_email(
|
||||
pool: Data<PgPool>,
|
||||
redis: Data<RedisPool>,
|
||||
@@ -2498,7 +2620,16 @@ pub async fn verify_email(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("email/subscribe")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "subscribeNewsletter",
|
||||
responses(
|
||||
(status = 204, description = "Newsletter subscription toggled"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[post("/email/subscribe")]
|
||||
pub async fn subscribe_newsletter(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -2535,7 +2666,16 @@ pub async fn subscribe_newsletter(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[get("email/subscribe")]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getNewsletterSubscriptionStatus",
|
||||
responses(
|
||||
(status = 200, description = "Subscription status"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[get("/email/subscribe")]
|
||||
pub async fn get_newsletter_subscription_status(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::routes::ApiError;
|
||||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::scope("gdpr").service(export));
|
||||
cfg.service(web::scope("/gdpr").service(export));
|
||||
}
|
||||
|
||||
#[post("/export")]
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::routes::ApiError;
|
||||
use crate::util::guards::medal_key_guard;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::scope("medal").service(verify).service(redeem));
|
||||
cfg.service(web::scope("/medal").service(verify).service(redeem));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -22,13 +22,45 @@ use crate::util::cors::default_cors;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
actix_web::web::scope("_internal")
|
||||
actix_web::web::scope("/_internal")
|
||||
.wrap(default_cors())
|
||||
.configure(admin::config)
|
||||
.configure(|cfg| {
|
||||
cfg.service(
|
||||
actix_web::web::scope("/admin")
|
||||
.service(admin::count_download)
|
||||
.service(admin::force_reindex),
|
||||
);
|
||||
cfg.service(
|
||||
actix_web::web::scope("/session")
|
||||
.service(session::list)
|
||||
.service(session::delete)
|
||||
.service(session::refresh),
|
||||
);
|
||||
cfg.service(
|
||||
actix_web::web::scope("/auth")
|
||||
.service(flows::init)
|
||||
.service(flows::auth_callback)
|
||||
.service(flows::delete_auth_provider)
|
||||
.service(flows::create_account_with_password)
|
||||
.service(flows::login_password)
|
||||
.service(flows::login_2fa)
|
||||
.service(flows::begin_2fa_flow)
|
||||
.service(flows::finish_2fa_flow)
|
||||
.service(flows::remove_2fa)
|
||||
.service(flows::reset_password_begin)
|
||||
.service(flows::change_password)
|
||||
.service(flows::resend_verify_email)
|
||||
.service(flows::set_email)
|
||||
.service(flows::verify_email)
|
||||
.service(flows::subscribe_newsletter)
|
||||
.service(flows::get_newsletter_subscription_status),
|
||||
);
|
||||
cfg.service(pats::get_pats);
|
||||
cfg.service(pats::create_pat);
|
||||
cfg.service(pats::edit_pat);
|
||||
cfg.service(pats::delete_pat);
|
||||
})
|
||||
.configure(oauth_clients::config)
|
||||
.configure(session::config)
|
||||
.configure(flows::config)
|
||||
.configure(pats::config)
|
||||
.configure(billing::config)
|
||||
.configure(gdpr::config)
|
||||
.configure(gotenberg::config)
|
||||
|
||||
@@ -22,14 +22,23 @@ use crate::util::validate::validation_errors_to_string;
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(get_pats);
|
||||
cfg.service(create_pat);
|
||||
cfg.service(edit_pat);
|
||||
cfg.service(delete_pat);
|
||||
}
|
||||
|
||||
#[get("pat")]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getPats",
|
||||
responses(
|
||||
(status = 200, description = "List of PATs"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["PAT_READ"]))
|
||||
)]
|
||||
#[get("/pat")]
|
||||
pub async fn get_pats(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -65,7 +74,7 @@ pub async fn get_pats(
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct NewPersonalAccessToken {
|
||||
pub scopes: Scopes,
|
||||
#[validate(length(min = 3, max = 255))]
|
||||
@@ -73,7 +82,17 @@ pub struct NewPersonalAccessToken {
|
||||
pub expires: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[post("pat")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "createPat",
|
||||
responses(
|
||||
(status = 200, description = "PAT created"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["PAT_CREATE"]))
|
||||
)]
|
||||
#[post("/pat")]
|
||||
pub async fn create_pat(
|
||||
req: HttpRequest,
|
||||
info: web::Json<NewPersonalAccessToken>,
|
||||
@@ -158,7 +177,7 @@ pub async fn create_pat(
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct ModifyPersonalAccessToken {
|
||||
pub scopes: Option<Scopes>,
|
||||
#[validate(length(min = 3, max = 255))]
|
||||
@@ -166,7 +185,18 @@ pub struct ModifyPersonalAccessToken {
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[patch("pat/{id}")]
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "editPat",
|
||||
params(("id" = String, Path, description = "The PAT ID")),
|
||||
responses(
|
||||
(status = 204, description = "PAT updated"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["PAT_WRITE"]))
|
||||
)]
|
||||
#[patch("/pat/{id}")]
|
||||
pub async fn edit_pat(
|
||||
req: HttpRequest,
|
||||
id: web::Path<(String,)>,
|
||||
@@ -263,7 +293,17 @@ pub async fn edit_pat(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[delete("pat/{id}")]
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deletePat",
|
||||
params(("id" = String, Path, description = "The PAT ID")),
|
||||
responses(
|
||||
(status = 204, description = "PAT deleted"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["PAT_DELETE"]))
|
||||
)]
|
||||
#[delete("/pat/{id}")]
|
||||
pub async fn delete_pat(
|
||||
req: HttpRequest,
|
||||
id: web::Path<(String,)>,
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::models::sessions::Session;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::http::header::AUTHORIZATION;
|
||||
use actix_web::web::{Data, ServiceConfig, scope};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rand::distributions::Alphanumeric;
|
||||
@@ -19,9 +19,9 @@ use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use woothee::parser::Parser;
|
||||
|
||||
pub fn config(cfg: &mut ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
scope("session")
|
||||
utoipa_actix_web::scope("/session")
|
||||
.service(list)
|
||||
.service(delete)
|
||||
.service(refresh),
|
||||
@@ -133,7 +133,16 @@ pub async fn issue_session(
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
#[get("list")]
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "listSessions",
|
||||
responses(
|
||||
(status = 200, description = "List of active sessions"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["SESSION_READ"]))
|
||||
)]
|
||||
#[get("/list")]
|
||||
pub async fn list(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -169,7 +178,17 @@ pub async fn list(
|
||||
Ok(HttpResponse::Ok().json(sessions))
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteSession",
|
||||
params(("id" = String, Path, description = "The session ID")),
|
||||
responses(
|
||||
(status = 204, description = "Session deleted"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["SESSION_DELETE"]))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn delete(
|
||||
info: web::Path<(String,)>,
|
||||
req: HttpRequest,
|
||||
@@ -209,7 +228,15 @@ pub async fn delete(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[post("refresh")]
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "refreshSession",
|
||||
responses(
|
||||
(status = 200, description = "Session refreshed"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
)
|
||||
)]
|
||||
#[post("/refresh")]
|
||||
pub async fn refresh(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
|
||||
@@ -25,17 +25,17 @@ pub use self::not_found::not_found;
|
||||
|
||||
pub fn root_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("maven")
|
||||
web::scope("/maven")
|
||||
.wrap(default_cors())
|
||||
.configure(maven::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("updates")
|
||||
web::scope("/updates")
|
||||
.wrap(default_cors())
|
||||
.configure(updates::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("analytics")
|
||||
web::scope("/analytics")
|
||||
.wrap(
|
||||
Cors::default()
|
||||
.allowed_origin_fn(|origin, _req_head| {
|
||||
@@ -59,7 +59,7 @@ pub fn root_config(cfg: &mut web::ServiceConfig) {
|
||||
.configure(analytics::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("api/v1")
|
||||
web::scope("/api/v1")
|
||||
.wrap(default_cors())
|
||||
.wrap_fn(|req, _srv| {
|
||||
async {
|
||||
|
||||
@@ -15,12 +15,13 @@ mod versions;
|
||||
pub use super::ApiError;
|
||||
use crate::util::cors::default_cors;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
pub fn utoipa_config(
|
||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||
) {
|
||||
cfg.service(
|
||||
actix_web::web::scope("v2")
|
||||
utoipa_actix_web::scope("/v2")
|
||||
.wrap(default_cors())
|
||||
.configure(super::internal::admin::config)
|
||||
// Todo: separate these- they need to also follow v2-v3 conversion
|
||||
.configure(super::internal::session::config)
|
||||
.configure(super::internal::flows::config)
|
||||
.configure(super::internal::pats::config)
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::{database::redis::RedisPool, routes::v2_reroute};
|
||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(web::scope("moderation").service(get_projects));
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(utoipa_actix_web::scope("/moderation").service(get_projects));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -22,7 +22,31 @@ fn default_count() -> u16 {
|
||||
100
|
||||
}
|
||||
|
||||
#[get("projects")]
|
||||
/// Get projects in the moderation queue.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getModerationProjects",
|
||||
params(
|
||||
(
|
||||
"count" = Option<u16>,
|
||||
Query,
|
||||
description = "Maximum number of projects to return"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_READ"]))
|
||||
)]
|
||||
#[get("/projects")]
|
||||
pub async fn get_projects(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -10,13 +10,12 @@ use crate::routes::v3;
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(notifications_get);
|
||||
cfg.service(notifications_delete);
|
||||
cfg.service(notifications_read);
|
||||
|
||||
cfg.service(
|
||||
web::scope("notification")
|
||||
utoipa_actix_web::scope("/notification")
|
||||
.service(notification_get)
|
||||
.service(notification_read)
|
||||
.service(notification_delete),
|
||||
@@ -28,7 +27,31 @@ pub struct NotificationIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[get("notifications")]
|
||||
/// Get multiple notifications by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getNotifications",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of notification IDs"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_READ"]))
|
||||
)]
|
||||
#[get("/notifications")]
|
||||
pub async fn notifications_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
@@ -57,7 +80,25 @@ pub async fn notifications_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{id}")]
|
||||
/// Get a notification by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getNotification",
|
||||
params(("id" = NotificationId, Path, description = "The ID of the notification")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_READ"]))
|
||||
)]
|
||||
#[get("/{id}")]
|
||||
pub async fn notification_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
@@ -83,7 +124,25 @@ pub async fn notification_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
/// Mark a notification as read.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "readNotification",
|
||||
params(("id" = NotificationId, Path, description = "The ID of the notification")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
pub async fn notification_read(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
@@ -97,7 +156,25 @@ pub async fn notification_read(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
/// Delete a notification by ID.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteNotification",
|
||||
params(("id" = NotificationId, Path, description = "The ID of the notification")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn notification_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
@@ -117,7 +194,31 @@ pub async fn notification_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[patch("notifications")]
|
||||
/// Mark multiple notifications as read.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "readNotifications",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of notification IDs"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_WRITE"]))
|
||||
)]
|
||||
#[patch("/notifications")]
|
||||
pub async fn notifications_read(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
@@ -137,7 +238,31 @@ pub async fn notifications_read(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("notifications")]
|
||||
/// Delete multiple notifications by ID.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteNotifications",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of notification IDs"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_WRITE"]))
|
||||
)]
|
||||
#[delete("/notifications")]
|
||||
pub async fn notifications_delete(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
|
||||
@@ -25,7 +25,7 @@ use validator::Validate;
|
||||
|
||||
use super::version_creation::InitialVersionData;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(project_create);
|
||||
}
|
||||
|
||||
@@ -134,6 +134,24 @@ struct ProjectCreateData {
|
||||
pub organization_id: Option<models::ids::OrganizationId>,
|
||||
}
|
||||
|
||||
/// Create a new project with initial versions.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "createProject",
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing `data` and uploaded files"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_CREATE"]))
|
||||
)]
|
||||
#[post("/project")]
|
||||
pub async fn project_create(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -21,14 +21,13 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(project_search);
|
||||
cfg.service(projects_get);
|
||||
cfg.service(projects_edit);
|
||||
cfg.service(random_projects_get);
|
||||
|
||||
cfg.service(
|
||||
web::scope("project")
|
||||
utoipa_actix_web::scope("/project")
|
||||
.service(project_get)
|
||||
.service(project_get_check)
|
||||
.service(project_delete)
|
||||
@@ -42,7 +41,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.service(project_unfollow)
|
||||
.service(super::teams::team_members_get_project)
|
||||
.service(
|
||||
web::scope("{project_id}")
|
||||
utoipa_actix_web::scope("/{project_id}")
|
||||
.service(super::versions::version_list)
|
||||
.service(super::versions::version_project_get)
|
||||
.service(dependency_list),
|
||||
@@ -50,7 +49,43 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[get("search")]
|
||||
/// Search projects.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "searchProjects",
|
||||
params(
|
||||
(
|
||||
"query" = Option<String>,
|
||||
Query,
|
||||
description = "The query to search for"
|
||||
),
|
||||
(
|
||||
"facets" = Option<String>,
|
||||
Query,
|
||||
description = "Search facets JSON"
|
||||
),
|
||||
(
|
||||
"index" = Option<String>,
|
||||
Query,
|
||||
description = "Search index to use"
|
||||
),
|
||||
(
|
||||
"offset" = Option<String>,
|
||||
Query,
|
||||
description = "Search result offset"
|
||||
),
|
||||
(
|
||||
"limit" = Option<String>,
|
||||
Query,
|
||||
description = "Maximum number of search results"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[get("/search")]
|
||||
pub async fn project_search(
|
||||
web::Query(info): web::Query<SearchRequest>,
|
||||
search_backend: web::Data<dyn SearchBackend>,
|
||||
@@ -141,13 +176,29 @@ fn parse_facet(facet: &str) -> Option<(String, String, String)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct RandomProjects {
|
||||
#[validate(range(min = 1, max = 100))]
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[get("projects_random")]
|
||||
/// Get random projects.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "randomProjects",
|
||||
params(
|
||||
(
|
||||
"count" = u32,
|
||||
Query,
|
||||
description = "Number of projects to return"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[get("/projects_random")]
|
||||
pub async fn random_projects_get(
|
||||
web::Query(count): web::Query<RandomProjects>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -174,7 +225,20 @@ pub async fn random_projects_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("projects")]
|
||||
/// Get multiple projects by ID or slug.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getProjects",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of project IDs or slugs"
|
||||
)
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
)]
|
||||
#[get("/projects")]
|
||||
pub async fn projects_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ProjectIds>,
|
||||
@@ -205,7 +269,20 @@ pub async fn projects_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{id}")]
|
||||
/// Get a project by ID or slug.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{id}")]
|
||||
pub async fn project_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -241,7 +318,20 @@ pub async fn project_get(
|
||||
}
|
||||
|
||||
//checks the validity of a project id or slug
|
||||
#[get("{id}/check")]
|
||||
/// Check that a project ID or slug exists.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "checkProjectValidity",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{id}/check")]
|
||||
pub async fn project_get_check(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -253,13 +343,26 @@ pub async fn project_get_check(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
struct DependencyInfo {
|
||||
pub projects: Vec<LegacyProject>,
|
||||
pub versions: Vec<LegacyVersion>,
|
||||
}
|
||||
|
||||
#[get("dependencies")]
|
||||
/// Get dependency projects and versions for a project.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getDependencies",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/dependencies")]
|
||||
pub async fn dependency_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -305,7 +408,7 @@ pub async fn dependency_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditProject {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -404,7 +507,26 @@ pub struct EditProject {
|
||||
pub monetization_status: Option<MonetizationStatus>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
/// Modify a project.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "modifyProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
request_body = EditProject,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn project_edit(
|
||||
req: HttpRequest,
|
||||
@@ -579,7 +701,7 @@ pub async fn project_edit(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct BulkEditProject {
|
||||
#[validate(length(max = 3))]
|
||||
pub categories: Option<Vec<String>>,
|
||||
@@ -642,7 +764,29 @@ pub struct BulkEditProject {
|
||||
pub discord_url: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[patch("projects")]
|
||||
/// Bulk-edit multiple projects.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "patchProjects",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of project IDs or slugs"
|
||||
)
|
||||
),
|
||||
request_body = BulkEditProject,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[patch("/projects")]
|
||||
pub async fn projects_edit(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ProjectIds>,
|
||||
@@ -739,12 +883,40 @@ pub async fn projects_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct Extension {
|
||||
pub ext: String,
|
||||
}
|
||||
|
||||
#[patch("{id}/icon")]
|
||||
/// Change a project's icon.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "changeProjectIcon",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
(
|
||||
"ext" = String,
|
||||
Query,
|
||||
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
|
||||
)
|
||||
),
|
||||
request_body(
|
||||
content(
|
||||
("image/png"),
|
||||
("image/jpeg"),
|
||||
("image/bmp"),
|
||||
("image/gif"),
|
||||
("image/webp"),
|
||||
("image/svg+xml")
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}/icon")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn project_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
@@ -771,7 +943,22 @@ pub async fn project_icon_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}/icon")]
|
||||
/// Delete a project's icon.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteProjectIcon",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}/icon")]
|
||||
pub async fn delete_project_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -793,7 +980,7 @@ pub async fn delete_project_icon(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct GalleryCreateQuery {
|
||||
pub featured: bool,
|
||||
#[validate(length(min = 1, max = 255))]
|
||||
@@ -803,7 +990,63 @@ pub struct GalleryCreateQuery {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
#[post("{id}/gallery")]
|
||||
/// Add a gallery image to a project.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "addGalleryImage",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
(
|
||||
"ext" = String,
|
||||
Query,
|
||||
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
|
||||
),
|
||||
(
|
||||
"featured" = bool,
|
||||
Query,
|
||||
description = "Whether this image is featured"
|
||||
),
|
||||
(
|
||||
"title" = Option<String>,
|
||||
Query,
|
||||
description = "Image title"
|
||||
),
|
||||
(
|
||||
"description" = Option<String>,
|
||||
Query,
|
||||
description = "Image description"
|
||||
),
|
||||
(
|
||||
"ordering" = Option<i64>,
|
||||
Query,
|
||||
description = "Image ordering"
|
||||
)
|
||||
),
|
||||
request_body(
|
||||
content(
|
||||
("image/png"),
|
||||
("image/jpeg"),
|
||||
("image/bmp"),
|
||||
("image/gif"),
|
||||
("image/webp"),
|
||||
("image/svg+xml")
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[post("/{id}/gallery")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn add_gallery_item(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
@@ -837,7 +1080,7 @@ pub async fn add_gallery_item(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct GalleryEditQuery {
|
||||
/// The url of the gallery item to edit
|
||||
pub url: String,
|
||||
@@ -859,7 +1102,48 @@ pub struct GalleryEditQuery {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
#[patch("{id}/gallery")]
|
||||
/// Modify a gallery image.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "modifyGalleryImage",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
("url" = String, Query, description = "URL of the image to edit"),
|
||||
(
|
||||
"featured" = Option<bool>,
|
||||
Query,
|
||||
description = "Whether this image is featured"
|
||||
),
|
||||
(
|
||||
"title" = Option<Option<String>>,
|
||||
Query,
|
||||
description = "Image title"
|
||||
),
|
||||
(
|
||||
"description" = Option<Option<String>>,
|
||||
Query,
|
||||
description = "Image description"
|
||||
),
|
||||
(
|
||||
"ordering" = Option<i64>,
|
||||
Query,
|
||||
description = "Image ordering"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}/gallery")]
|
||||
pub async fn edit_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryEditQuery>,
|
||||
@@ -885,12 +1169,30 @@ pub async fn edit_gallery_item(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct GalleryDeleteQuery {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[delete("{id}/gallery")]
|
||||
/// Delete a gallery image.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteGalleryImage",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
("url" = String, Query, description = "URL of the image to delete")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}/gallery")]
|
||||
pub async fn delete_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||
@@ -912,7 +1214,22 @@ pub async fn delete_gallery_item(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
/// Delete a project by ID or slug.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_DELETE"]))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn project_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -935,7 +1252,22 @@ pub async fn project_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[post("{id}/follow")]
|
||||
/// Follow a project.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "followProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_WRITE"]))
|
||||
)]
|
||||
#[post("/{id}/follow")]
|
||||
pub async fn project_follow(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -949,7 +1281,22 @@ pub async fn project_follow(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}/follow")]
|
||||
/// Unfollow a project.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "unfollowProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}/follow")]
|
||||
pub async fn project_unfollow(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(reports_get);
|
||||
cfg.service(reports);
|
||||
cfg.service(report_create);
|
||||
@@ -17,7 +17,21 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(report_get);
|
||||
}
|
||||
|
||||
#[post("report")]
|
||||
/// Create a report for a project, version, or user.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "submitReport",
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["REPORT_CREATE"]))
|
||||
)]
|
||||
#[post("/report")]
|
||||
pub async fn report_create(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -40,7 +54,7 @@ pub async fn report_create(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ReportsRequestOptions {
|
||||
#[serde(default = "default_count")]
|
||||
count: u16,
|
||||
@@ -55,7 +69,31 @@ fn default_all() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[get("report")]
|
||||
/// Get open reports for the current user.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getOpenReports",
|
||||
params(
|
||||
(
|
||||
"count" = Option<u16>,
|
||||
Query,
|
||||
description = "Maximum number of reports to return"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["REPORT_READ"]))
|
||||
)]
|
||||
#[get("/report")]
|
||||
pub async fn reports(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -88,12 +126,36 @@ pub async fn reports(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ReportIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[get("reports")]
|
||||
/// Get multiple reports by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getReports",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of report IDs"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["REPORT_READ"]))
|
||||
)]
|
||||
#[get("/reports")]
|
||||
pub async fn reports_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ReportIds>,
|
||||
@@ -122,7 +184,25 @@ pub async fn reports_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("report/{id}")]
|
||||
/// Get a report by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getReport",
|
||||
params(("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["REPORT_READ"]))
|
||||
)]
|
||||
#[get("/report/{id}")]
|
||||
pub async fn report_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -145,14 +225,34 @@ pub async fn report_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditReport {
|
||||
#[validate(length(max = 65536))]
|
||||
pub body: Option<String>,
|
||||
pub closed: Option<bool>,
|
||||
}
|
||||
|
||||
#[patch("report/{id}")]
|
||||
/// Modify a report.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "modifyReport",
|
||||
params(("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")),
|
||||
request_body = EditReport,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["REPORT_WRITE"]))
|
||||
)]
|
||||
#[patch("/report/{id}")]
|
||||
pub async fn report_edit(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -178,7 +278,25 @@ pub async fn report_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("report/{id}")]
|
||||
/// Delete a report by ID.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteReport",
|
||||
params(("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["REPORT_DELETE"]))
|
||||
)]
|
||||
#[delete("/report/{id}")]
|
||||
pub async fn report_delete(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::routes::{
|
||||
};
|
||||
use actix_web::{HttpResponse, get, web};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(get_stats);
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct V2Stats {
|
||||
pub projects: Option<i64>,
|
||||
pub versions: Option<i64>,
|
||||
@@ -17,7 +17,19 @@ pub struct V2Stats {
|
||||
pub files: Option<i64>,
|
||||
}
|
||||
|
||||
#[get("statistics")]
|
||||
/// Get aggregate instance statistics.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "statistics",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = V2Stats
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/statistics")]
|
||||
pub async fn get_stats(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
|
||||
@@ -12,9 +12,9 @@ use actix_web::{HttpResponse, get, web};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("tag")
|
||||
utoipa_actix_web::scope("/tag")
|
||||
.service(category_list)
|
||||
.service(loader_list)
|
||||
.service(game_version_list)
|
||||
@@ -27,7 +27,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct CategoryData {
|
||||
pub icon: String,
|
||||
pub name: String,
|
||||
@@ -35,7 +35,19 @@ pub struct CategoryData {
|
||||
pub header: String,
|
||||
}
|
||||
|
||||
#[get("category")]
|
||||
/// Get the list of project categories.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "categoryList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<CategoryData>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/category")]
|
||||
pub async fn category_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -62,14 +74,26 @@ pub async fn category_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct LoaderData {
|
||||
pub icon: String,
|
||||
pub name: String,
|
||||
pub supported_project_types: Vec<String>,
|
||||
}
|
||||
|
||||
#[get("loader")]
|
||||
/// Get the list of loaders.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "loaderList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<LoaderData>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/loader")]
|
||||
pub async fn loader_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -116,7 +140,7 @@ pub async fn loader_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[derive(serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct GameVersionQueryData {
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
@@ -124,14 +148,38 @@ pub struct GameVersionQueryData {
|
||||
pub major: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct GameVersionQuery {
|
||||
#[serde(rename = "type")]
|
||||
type_: Option<String>,
|
||||
major: Option<bool>,
|
||||
}
|
||||
|
||||
#[get("game_version")]
|
||||
/// Get the list of game versions.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "versionList",
|
||||
params(
|
||||
(
|
||||
"type" = Option<String>,
|
||||
Query,
|
||||
description = "Optional game version type filter"
|
||||
),
|
||||
(
|
||||
"major" = Option<bool>,
|
||||
Query,
|
||||
description = "Whether to return only major versions"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<GameVersionQueryData>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/game_version")]
|
||||
pub async fn game_version_list(
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<GameVersionQuery>,
|
||||
@@ -185,13 +233,25 @@ pub async fn game_version_list(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct License {
|
||||
pub short: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[get("license")]
|
||||
/// Get SPDX license identifiers and names.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "licenseList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<License>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/license")]
|
||||
pub async fn license_list() -> HttpResponse {
|
||||
let response = v3::tags::license_list().await;
|
||||
|
||||
@@ -212,13 +272,27 @@ pub async fn license_list() -> HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[derive(serde::Serialize, utoipa::ToSchema)]
|
||||
pub struct LicenseText {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[get("license/{id}")]
|
||||
/// Get full license text by SPDX ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "licenseText",
|
||||
params(("id" = String, Path, description = "The license ID to get the text for")),
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = LicenseText
|
||||
),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[get("/license/{id}")]
|
||||
pub async fn license_text(
|
||||
params: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -240,7 +314,9 @@ pub async fn license_text(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)]
|
||||
#[derive(
|
||||
serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug, utoipa::ToSchema,
|
||||
)]
|
||||
pub struct DonationPlatformQueryData {
|
||||
// The difference between name and short is removed in v3.
|
||||
// Now, the 'id' becomes the name, and the 'name' is removed (the frontend uses the id as the name)
|
||||
@@ -249,7 +325,19 @@ pub struct DonationPlatformQueryData {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[get("donation_platform")]
|
||||
/// Get available donation platforms.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "donationPlatformList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<DonationPlatformQueryData>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/donation_platform")]
|
||||
pub async fn donation_platform_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -295,7 +383,19 @@ pub async fn donation_platform_list(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[get("report_type")]
|
||||
/// Get valid report types.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "reportTypeList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<String>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/report_type")]
|
||||
pub async fn report_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -306,7 +406,19 @@ pub async fn report_type_list(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[get("project_type")]
|
||||
/// Get valid project types.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "projectTypeList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<String>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/project_type")]
|
||||
pub async fn project_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -317,7 +429,19 @@ pub async fn project_type_list(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[get("side_type")]
|
||||
/// Get valid side-type values.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "sideTypeList",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Expected response to a valid request",
|
||||
body = Vec<String>
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/side_type")]
|
||||
pub async fn side_type_list() -> Result<HttpResponse, ApiError> {
|
||||
// Original side types are no longer reflected in the database.
|
||||
// Therefore, we hardcode and return all the fields that are supported by our v2 conversion logic.
|
||||
|
||||
@@ -12,11 +12,10 @@ use ariadne::ids::UserId;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(teams_get);
|
||||
|
||||
cfg.service(
|
||||
web::scope("team")
|
||||
utoipa_actix_web::scope("/team")
|
||||
.service(team_members_get)
|
||||
.service(edit_team_member)
|
||||
.service(transfer_ownership)
|
||||
@@ -31,7 +30,20 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
// also the members of the organization's team if the project is associated with an organization
|
||||
// (Unlike team_members_get_project, which only returns the members of the project's team)
|
||||
// They can be differentiated by the "organization_permissions" field being null or not
|
||||
#[get("{id}/members")]
|
||||
/// Get a project's team members.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getProjectTeamMembers",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{id}/members")]
|
||||
pub async fn team_members_get_project(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -62,7 +74,15 @@ pub async fn team_members_get_project(
|
||||
}
|
||||
|
||||
// Returns all members of a team, but not necessarily those of a project-team's organization (unlike team_members_get_project)
|
||||
#[get("{id}/members")]
|
||||
/// Get a team's members.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getTeamMembers",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
responses((status = 200, description = "Expected response to a valid request")),
|
||||
security(("bearer_auth" = ["PROJECT_READ"]))
|
||||
)]
|
||||
#[get("/{id}/members")]
|
||||
pub async fn team_members_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -87,12 +107,19 @@ pub async fn team_members_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct TeamIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[get("teams")]
|
||||
/// Get the members of multiple teams.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getTeams",
|
||||
params(("ids" = String, Query, description = "The JSON array of team IDs")),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
)]
|
||||
#[get("/teams")]
|
||||
pub async fn teams_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<TeamIds>,
|
||||
@@ -127,7 +154,25 @@ pub async fn teams_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("{id}/join")]
|
||||
/// Join a team with a pending invite.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "joinTeam",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[post("/{id}/join")]
|
||||
pub async fn join_team(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -149,7 +194,7 @@ fn default_ordering() -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct NewTeamMember {
|
||||
pub user_id: UserId,
|
||||
#[serde(default = "default_role")]
|
||||
@@ -165,7 +210,26 @@ pub struct NewTeamMember {
|
||||
pub ordering: i64,
|
||||
}
|
||||
|
||||
#[post("{id}/members")]
|
||||
/// Add a member to a team.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "addTeamMember",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
request_body = NewTeamMember,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[post("/{id}/members")]
|
||||
pub async fn add_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -194,7 +258,7 @@ pub async fn add_team_member(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct EditTeamMember {
|
||||
pub permissions: Option<ProjectPermissions>,
|
||||
pub organization_permissions: Option<OrganizationPermissions>,
|
||||
@@ -203,7 +267,33 @@ pub struct EditTeamMember {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
#[patch("{id}/members/{user_id}")]
|
||||
/// Modify a team member.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "modifyTeamMember",
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team"),
|
||||
(
|
||||
"user_id" = UserId,
|
||||
Path,
|
||||
description = "The ID of the user to modify"
|
||||
)
|
||||
),
|
||||
request_body = EditTeamMember,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}/members/{user_id}")]
|
||||
pub async fn edit_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
@@ -231,12 +321,31 @@ pub async fn edit_team_member(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct TransferOwnership {
|
||||
pub user_id: UserId,
|
||||
}
|
||||
|
||||
#[patch("{id}/owner")]
|
||||
/// Transfer team ownership.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "transferTeamOwnership",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
request_body = TransferOwnership,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}/owner")]
|
||||
pub async fn transfer_ownership(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -260,7 +369,32 @@ pub async fn transfer_ownership(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}/members/{user_id}")]
|
||||
/// Remove a member from a team.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteTeamMember",
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team"),
|
||||
(
|
||||
"user_id" = UserId,
|
||||
Path,
|
||||
description = "The ID of the user to remove"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}/members/{user_id}")]
|
||||
pub async fn remove_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
|
||||
@@ -11,17 +11,31 @@ use crate::routes::{ApiError, v2_reroute, v3};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("thread")
|
||||
utoipa_actix_web::scope("/thread")
|
||||
.service(thread_get)
|
||||
.service(thread_send_message),
|
||||
);
|
||||
cfg.service(web::scope("message").service(message_delete));
|
||||
cfg.service(utoipa_actix_web::scope("/message").service(message_delete));
|
||||
cfg.service(threads_get);
|
||||
}
|
||||
|
||||
#[get("{id}")]
|
||||
/// Get a thread by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getThread",
|
||||
params(("id" = ThreadId, Path, description = "The ID of the thread")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["THREAD_READ"]))
|
||||
)]
|
||||
#[get("/{id}")]
|
||||
pub async fn thread_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
@@ -34,12 +48,26 @@ pub async fn thread_get(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ThreadIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[get("threads")]
|
||||
/// Get multiple threads by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getThreads",
|
||||
params(("ids" = String, Query, description = "The JSON array of thread IDs")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["THREAD_READ"]))
|
||||
)]
|
||||
#[get("/threads")]
|
||||
pub async fn threads_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ThreadIds>,
|
||||
@@ -70,12 +98,28 @@ pub async fn threads_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct NewThreadMessage {
|
||||
pub body: MessageBody,
|
||||
}
|
||||
|
||||
#[post("{id}")]
|
||||
/// Send a message to a thread.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "sendThreadMessage",
|
||||
params(("id" = ThreadId, Path, description = "The ID of the thread")),
|
||||
request_body = NewThreadMessage,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["THREAD_WRITE"]))
|
||||
)]
|
||||
#[post("/{id}")]
|
||||
pub async fn thread_send_message(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
@@ -100,7 +144,25 @@ pub async fn thread_send_message(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
/// Delete a thread message by ID.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteThreadMessage",
|
||||
params(("id" = ThreadMessageId, Path, description = "The ID of the message")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["THREAD_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn message_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadMessageId,)>,
|
||||
|
||||
@@ -14,12 +14,11 @@ use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(user_auth_get);
|
||||
cfg.service(users_get);
|
||||
|
||||
cfg.service(
|
||||
web::scope("user")
|
||||
utoipa_actix_web::scope("/user")
|
||||
.service(user_get)
|
||||
.service(projects_list)
|
||||
.service(user_delete)
|
||||
@@ -31,7 +30,20 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[get("user")]
|
||||
/// Get the current user from the authorization header.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getUserFromAuth",
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_READ"]))
|
||||
)]
|
||||
#[get("/user")]
|
||||
pub async fn user_auth_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -52,12 +64,19 @@ pub async fn user_auth_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UserIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[get("users")]
|
||||
/// Get multiple users by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getUsers",
|
||||
params(("ids" = String, Query, description = "The JSON array of user IDs")),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
)]
|
||||
#[get("/users")]
|
||||
pub async fn users_get(
|
||||
web::Query(ids): web::Query<UserIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -82,7 +101,20 @@ pub async fn users_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{id}")]
|
||||
/// Get a user by ID or username.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getUser",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{id}")]
|
||||
pub async fn user_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -104,7 +136,20 @@ pub async fn user_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{user_id}/projects")]
|
||||
/// Get a user's projects.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getUserProjects",
|
||||
params(("user_id" = String, Path, description = "The ID or username of the user")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{user_id}/projects")]
|
||||
pub async fn projects_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -133,7 +178,7 @@ pub async fn projects_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditUser {
|
||||
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_USERNAME))]
|
||||
pub username: Option<String>,
|
||||
@@ -156,7 +201,26 @@ pub struct EditUser {
|
||||
pub allow_friend_requests: Option<bool>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
/// Modify a user.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "modifyUser",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
request_body = EditUser,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
pub async fn user_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -186,12 +250,44 @@ pub async fn user_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct Extension {
|
||||
pub ext: String,
|
||||
}
|
||||
|
||||
#[patch("{id}/icon")]
|
||||
/// Change a user's avatar.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "changeUserIcon",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user"),
|
||||
(
|
||||
"ext" = String,
|
||||
Query,
|
||||
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
|
||||
)
|
||||
),
|
||||
request_body(
|
||||
content(
|
||||
("image/png"),
|
||||
("image/jpeg"),
|
||||
("image/bmp"),
|
||||
("image/gif"),
|
||||
("image/webp"),
|
||||
("image/svg+xml")
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}/icon")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn user_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
@@ -218,7 +314,22 @@ pub async fn user_icon_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}/icon")]
|
||||
/// Remove a user's avatar.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteUserIcon",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_WRITE"]))
|
||||
)]
|
||||
#[delete("/{id}/icon")]
|
||||
pub async fn user_icon_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -240,7 +351,25 @@ pub async fn user_icon_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
/// Delete a user by ID or username.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteUser",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_DELETE"]))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn user_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -255,7 +384,25 @@ pub async fn user_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[get("{id}/follows")]
|
||||
/// Get projects followed by a user.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getFollowedProjects",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["USER_READ"]))
|
||||
)]
|
||||
#[get("/{id}/follows")]
|
||||
pub async fn user_follows(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -284,7 +431,25 @@ pub async fn user_follows(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{id}/notifications")]
|
||||
/// Get notifications for a user.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getUserNotifications",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["NOTIFICATION_READ"]))
|
||||
)]
|
||||
#[get("/{id}/notifications")]
|
||||
pub async fn user_notifications(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
|
||||
@@ -75,7 +75,25 @@ pub struct InitialVersionData {
|
||||
}
|
||||
|
||||
// under `/api/v1/version`
|
||||
#[post("version")]
|
||||
/// Create a version on an existing project.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "createVersion",
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing `data` and uploaded files"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_CREATE"]))
|
||||
)]
|
||||
#[post("/version")]
|
||||
pub async fn version_create(
|
||||
req: HttpRequest,
|
||||
payload: Multipart,
|
||||
@@ -280,7 +298,29 @@ async fn get_example_version_fields(
|
||||
}
|
||||
|
||||
// under /api/v1/version/{version_id}
|
||||
#[post("{version_id}/file")]
|
||||
/// Add files to an existing version.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "addFilesToVersion",
|
||||
params(("version_id" = VersionId, Path, description = "The ID of the version")),
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing files to upload"
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_WRITE"]))
|
||||
)]
|
||||
#[post("/{version_id}/file")]
|
||||
pub async fn upload_file_to_version(
|
||||
req: HttpRequest,
|
||||
url_data: web::Path<(VersionId,)>,
|
||||
|
||||
@@ -11,9 +11,9 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("version_file")
|
||||
utoipa_actix_web::scope("/version_file")
|
||||
.service(delete_file)
|
||||
.service(get_version_from_hash)
|
||||
.service(download_version)
|
||||
@@ -22,7 +22,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
|
||||
cfg.service(
|
||||
web::scope("version_files")
|
||||
utoipa_actix_web::scope("/version_files")
|
||||
.service(get_versions_from_hashes)
|
||||
.service(update_files)
|
||||
.service(update_files_many)
|
||||
@@ -31,7 +31,36 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
}
|
||||
|
||||
// under /api/v1/version_file/{hash}
|
||||
#[get("{version_id}")]
|
||||
/// Get version metadata by file hash.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "versionFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID when hash maps to multiple files"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{version_id}")]
|
||||
pub async fn get_version_from_hash(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -62,7 +91,36 @@ pub async fn get_version_from_hash(
|
||||
}
|
||||
|
||||
// under /api/v1/version_file/{hash}/download
|
||||
#[get("{version_id}/download")]
|
||||
/// Download a file by hash.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "downloadVersionFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID when hash maps to multiple files"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 302, description = "Temporary redirect to file URL"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{version_id}/download")]
|
||||
pub async fn download_version(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -85,7 +143,41 @@ pub async fn download_version(
|
||||
}
|
||||
|
||||
// under /api/v1/version_file/{hash}
|
||||
#[delete("{version_id}")]
|
||||
/// Delete a file by hash.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteFileFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID to delete from"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_WRITE"]))
|
||||
)]
|
||||
#[delete("/{version_id}")]
|
||||
pub async fn delete_file(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -107,14 +199,45 @@ pub async fn delete_file(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateData {
|
||||
pub loaders: Option<Vec<String>>,
|
||||
pub game_versions: Option<Vec<String>>,
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
}
|
||||
|
||||
#[post("{version_id}/update")]
|
||||
/// Get the latest compatible version from a file hash.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "getLatestVersionFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID when hash maps to multiple files"
|
||||
)
|
||||
),
|
||||
request_body = UpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[post("/{version_id}/update")]
|
||||
pub async fn get_update_from_hash(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -162,13 +285,23 @@ pub async fn get_update_from_hash(
|
||||
}
|
||||
|
||||
// Requests above with multiple versions below
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct FileHashes {
|
||||
pub algorithm: Option<String>,
|
||||
pub hashes: Vec<String>,
|
||||
}
|
||||
|
||||
// under /api/v2/version_files
|
||||
/// Get versions from file hashes.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "versionsFromHashes",
|
||||
request_body = FileHashes,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[post("")]
|
||||
pub async fn get_versions_from_hashes(
|
||||
req: HttpRequest,
|
||||
@@ -210,7 +343,17 @@ pub async fn get_versions_from_hashes(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("project")]
|
||||
/// Get projects from file hashes.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "projectsFromHashes",
|
||||
request_body = FileHashes,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[post("/project")]
|
||||
pub async fn get_projects_from_hashes(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -268,7 +411,7 @@ pub async fn get_projects_from_hashes(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ManyUpdateData {
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
pub hashes: Vec<String>,
|
||||
@@ -277,7 +420,17 @@ pub struct ManyUpdateData {
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
}
|
||||
|
||||
#[post("update")]
|
||||
/// Get latest compatible versions for multiple hashes.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "getLatestVersionsFromHashes",
|
||||
request_body = ManyUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[post("/update")]
|
||||
pub async fn update_files(
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -316,7 +469,17 @@ pub async fn update_files(
|
||||
Ok(HttpResponse::Ok().json(v3_versions))
|
||||
}
|
||||
|
||||
#[post("update_many")]
|
||||
/// Get all latest compatible versions for multiple hashes.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "getLatestVersionsFromHashesMany",
|
||||
request_body = ManyUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[post("/update_many")]
|
||||
pub async fn update_files_many(
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -358,7 +521,7 @@ pub async fn update_files_many(
|
||||
Ok(HttpResponse::Ok().json(v3_versions))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct FileUpdateData {
|
||||
pub hash: String,
|
||||
pub loaders: Option<Vec<String>>,
|
||||
@@ -366,13 +529,23 @@ pub struct FileUpdateData {
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ManyFileUpdateData {
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
pub hashes: Vec<FileUpdateData>,
|
||||
}
|
||||
|
||||
#[post("update_individual")]
|
||||
/// Get latest versions with per-hash filters.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
operation_id = "getLatestVersionsFromHashesIndividual",
|
||||
request_body = ManyFileUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[post("/update_individual")]
|
||||
pub async fn update_individual_files(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -16,12 +16,11 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(versions_get);
|
||||
cfg.service(super::version_creation::version_create);
|
||||
|
||||
cfg.service(
|
||||
web::scope("version")
|
||||
utoipa_actix_web::scope("/version")
|
||||
.service(version_get)
|
||||
.service(version_delete)
|
||||
.service(version_edit)
|
||||
@@ -29,7 +28,7 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct VersionListFilters {
|
||||
pub game_versions: Option<String>,
|
||||
pub loaders: Option<String>,
|
||||
@@ -45,7 +44,46 @@ fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[get("version")]
|
||||
/// List versions for a project.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getProjectVersions",
|
||||
params(
|
||||
(
|
||||
"project_id" = String,
|
||||
Path,
|
||||
description = "The ID or slug of the project"
|
||||
),
|
||||
(
|
||||
"loaders" = Option<String>,
|
||||
Query,
|
||||
description = "JSON array of loaders to filter by"
|
||||
),
|
||||
(
|
||||
"game_versions" = Option<String>,
|
||||
Query,
|
||||
description = "JSON array of game versions to filter by"
|
||||
),
|
||||
(
|
||||
"featured" = Option<bool>,
|
||||
Query,
|
||||
description = "Filter by featured status"
|
||||
),
|
||||
(
|
||||
"include_changelog" = Option<bool>,
|
||||
Query,
|
||||
description = "Whether to include changelog fields"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/version")]
|
||||
pub async fn version_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -129,7 +167,31 @@ pub async fn version_list(
|
||||
}
|
||||
|
||||
// Given a project ID/slug and a version slug
|
||||
#[get("version/{slug}")]
|
||||
/// Get a project version by ID or version number.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getVersionFromIdOrNumber",
|
||||
params(
|
||||
(
|
||||
"project_id" = String,
|
||||
Path,
|
||||
description = "The ID or slug of the project"
|
||||
),
|
||||
(
|
||||
"slug" = String,
|
||||
Path,
|
||||
description = "The version ID or version number"
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/version/{slug}")]
|
||||
pub async fn version_project_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String, String)>,
|
||||
@@ -157,14 +219,21 @@ pub async fn version_project_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct VersionIds {
|
||||
pub ids: String,
|
||||
#[serde(default = "default_true")]
|
||||
pub include_changelog: bool,
|
||||
}
|
||||
|
||||
#[get("versions")]
|
||||
/// Get multiple versions by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getVersions",
|
||||
params(("ids" = String, Query, description = "The JSON array of version IDs")),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
)]
|
||||
#[get("/versions")]
|
||||
pub async fn versions_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<VersionIds>,
|
||||
@@ -199,7 +268,20 @@ pub async fn versions_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{version_id}")]
|
||||
/// Get a version by ID.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "getVersion",
|
||||
params(("version_id" = models::ids::VersionId, Path, description = "The ID of the version")),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{version_id}")]
|
||||
pub async fn version_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
@@ -223,7 +305,7 @@ pub async fn version_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditVersion {
|
||||
#[validate(
|
||||
length(min = 1, max = 64),
|
||||
@@ -251,14 +333,33 @@ pub struct EditVersion {
|
||||
pub file_types: Option<Vec<EditVersionFileType>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct EditVersionFileType {
|
||||
pub algorithm: String,
|
||||
pub hash: String,
|
||||
pub file_type: Option<FileType>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
/// Modify an existing version.
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
operation_id = "modifyVersion",
|
||||
params(("id" = VersionId, Path, description = "The ID of the version")),
|
||||
request_body = EditVersion,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_WRITE"]))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
pub async fn version_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(VersionId,)>,
|
||||
@@ -350,7 +451,25 @@ pub async fn version_edit(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[delete("{version_id}")]
|
||||
/// Delete a version by ID.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
operation_id = "deleteVersion",
|
||||
params(("version_id" = VersionId, Path, description = "The ID of the version")),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_DELETE"]))
|
||||
)]
|
||||
#[delete("/{version_id}")]
|
||||
pub async fn version_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(VersionId,)>,
|
||||
|
||||
Reference in New Issue
Block a user