mirror of
https://github.com/modrinth/code.git
synced 2026-09-04 22:10:15 +00:00
feat: better api docs (#6586)
* feat: docs * fix tombi * chore: fix some of the routes rendering with missing / * response schemas * fix: restore labrinth docs routes * Fix path parameter docs in routes * remove utoipa-actix-web * consistency * improve version intros * improve formatting, examples * better hash examples, move openapi stuff to openapi.rs * more utoipa param fixes * request body docs * chore: remove moderation route from v2, remove ingest & webhooks from v3 spec * fixes * chore: tweak sources titles * fix * fix test * improve examples * increase compiler spawned thread stack size * remove unused tests & script * test * bro what * fix --------- Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
This commit is contained in:
co-authored by
aecsocket
parent
b26d048a63
commit
4a6fa9fc3d
@@ -13,7 +13,7 @@ use crate::queue::moderation::ApprovalType;
|
||||
use crate::routes::ApiError;
|
||||
use crate::{auth::check_is_moderator_from_headers, queue::session::AuthQueue};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(search)
|
||||
.service(get_by_sha1)
|
||||
.service(get_by_sha1_bulk)
|
||||
@@ -329,9 +329,14 @@ async fn fetch_by_flame_ids(
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Search external licenses.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = inline(Vec<ExternalProject>)))
|
||||
)]
|
||||
#[post("/search")]
|
||||
async fn search(
|
||||
pub async fn search(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -393,9 +398,14 @@ async fn search(
|
||||
Ok(web::Json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Look up external license metadata.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalLicenseLookupResponse))
|
||||
)]
|
||||
#[post("/lookup")]
|
||||
async fn lookup(
|
||||
pub async fn lookup(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -422,9 +432,14 @@ async fn lookup(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get external license by SHA-1.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[get("/by-sha1/{sha1}")]
|
||||
async fn get_by_sha1(
|
||||
pub async fn get_by_sha1(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -448,9 +463,14 @@ async fn get_by_sha1(
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get external licenses by SHA-1.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = inline(HashMap<String, ExternalProject>)))
|
||||
)]
|
||||
#[post("/by-sha1")]
|
||||
async fn get_by_sha1_bulk(
|
||||
pub async fn get_by_sha1_bulk(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -472,9 +492,14 @@ async fn get_by_sha1_bulk(
|
||||
Ok(web::Json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Add an external license file.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[post("/file")]
|
||||
async fn add_file(
|
||||
pub async fn add_file(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -484,9 +509,14 @@ async fn add_file(
|
||||
upsert_file_license(req, pool, redis, session_queue, body).await
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Reassign an external license file.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[post("/file/reassign")]
|
||||
async fn reassign_file(
|
||||
pub async fn reassign_file(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -584,9 +614,14 @@ async fn upsert_file_license(
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Update an external license.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
async fn update_license(
|
||||
pub async fn update_license(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -21,11 +21,11 @@ use ownership::get_projects_ownership;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod external_license;
|
||||
pub mod external_license;
|
||||
mod ownership;
|
||||
mod tech_review;
|
||||
pub mod tech_review;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_projects)
|
||||
.service(get_project_meta)
|
||||
.service(set_project_meta)
|
||||
@@ -35,13 +35,9 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
.service(release_lock)
|
||||
.service(release_lock_beacon)
|
||||
.service(delete_all_locks)
|
||||
.service(web::scope("/tech-review").configure(tech_review::config))
|
||||
.service(
|
||||
utoipa_actix_web::scope("/tech-review")
|
||||
.configure(tech_review::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/external-license")
|
||||
.configure(external_license::config),
|
||||
web::scope("/external-license").configure(external_license::config),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,12 +158,19 @@ pub struct DeleteAllLocksResponse {
|
||||
pub deleted_count: u64,
|
||||
}
|
||||
|
||||
/// Fetch all projects which are in the moderation queue.
|
||||
/// List projects in the moderation queue.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
params(
|
||||
("count" = Option<u16>, Query),
|
||||
("offset" = Option<u32>, Query),
|
||||
("has_external_dependencies" = Option<bool>, Query)
|
||||
),
|
||||
responses((status = OK, body = inline(Vec<FetchedProject>)))
|
||||
)]
|
||||
#[get("/projects")]
|
||||
async fn get_projects(
|
||||
pub async fn get_projects(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -291,12 +294,14 @@ pub async fn get_projects_internal(
|
||||
Ok(web::Json(projects))
|
||||
}
|
||||
|
||||
/// Fetch moderation metadata for a specific project.
|
||||
/// Get project moderation metadata.
|
||||
#[utoipa::path(
|
||||
responses((status = OK, body = inline(Vec<Project>)))
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = MissingMetadata))
|
||||
)]
|
||||
#[get("/project/{id}")]
|
||||
async fn get_project_meta(
|
||||
pub async fn get_project_meta(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -447,10 +452,14 @@ pub enum Judgement {
|
||||
},
|
||||
}
|
||||
|
||||
/// Update moderation judgements for projects in the review queue.
|
||||
#[utoipa::path]
|
||||
/// Update project moderation judgements.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/project")]
|
||||
async fn set_project_meta(
|
||||
pub async fn set_project_meta(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -536,16 +545,18 @@ async fn set_project_meta(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Acquire or refresh a moderation lock on a project.
|
||||
/// Acquire a moderation lock.
|
||||
/// Returns success if acquired, or info about who holds the lock if blocked.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockAcquireResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[post("/lock/{project_id}")]
|
||||
async fn acquire_lock(
|
||||
pub async fn acquire_lock(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -594,15 +605,17 @@ async fn acquire_lock(
|
||||
}
|
||||
}
|
||||
|
||||
/// Force-acquire a moderation lock on a project (moderator override).
|
||||
/// Override a moderation lock.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockAcquireResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[post("/lock/{project_id}/override")]
|
||||
async fn override_lock(
|
||||
pub async fn override_lock(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -639,15 +652,17 @@ async fn override_lock(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Check the lock status for a project
|
||||
/// Get moderation lock status.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockStatusResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[get("/lock/{project_id}")]
|
||||
async fn get_lock_status(
|
||||
pub async fn get_lock_status(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -699,15 +714,17 @@ async fn get_lock_status(
|
||||
}
|
||||
}
|
||||
|
||||
/// Release a moderation lock on a project
|
||||
/// Release a moderation lock.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockReleaseResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[delete("/lock/{project_id}")]
|
||||
async fn release_lock(
|
||||
pub async fn release_lock(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -740,12 +757,14 @@ async fn release_lock(
|
||||
Ok(web::Json(LockReleaseResponse { success: released }))
|
||||
}
|
||||
|
||||
/// Release a moderation lock using credentials in the request body.
|
||||
/// Release a moderation lock by beacon.
|
||||
///
|
||||
/// For use with `navigator.sendBeacon`, which cannot set `Authorization` or send `DELETE`.
|
||||
/// The body must be `text/plain` containing the same token value as the `Authorization` header
|
||||
/// (optional `Bearer ` prefix). This avoids a CORS preflight compared to `application/json`.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
request_body(
|
||||
content = String,
|
||||
description = "Token value (same as Authorization header)",
|
||||
@@ -757,7 +776,7 @@ async fn release_lock(
|
||||
)
|
||||
)]
|
||||
#[post("/lock/{project_id}/release")]
|
||||
async fn release_lock_beacon(
|
||||
pub async fn release_lock_beacon(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -811,15 +830,17 @@ async fn release_lock_beacon(
|
||||
Ok(web::Json(LockReleaseResponse { success: released }))
|
||||
}
|
||||
|
||||
/// Delete all moderation locks (admin only)
|
||||
/// Delete all moderation locks.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = DeleteAllLocksResponse),
|
||||
(status = UNAUTHORIZED, description = "Not an admin")
|
||||
)
|
||||
)]
|
||||
#[delete("/locks")]
|
||||
async fn delete_all_locks(
|
||||
pub async fn delete_all_locks(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::{
|
||||
};
|
||||
use eyre::eyre;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(search_projects)
|
||||
.service(get_project_report)
|
||||
.service(get_report)
|
||||
@@ -193,13 +193,15 @@ pub enum FlagReason {
|
||||
Delphi,
|
||||
}
|
||||
|
||||
/// Get info on an issue in a Delphi report.
|
||||
/// Get a Delphi report issue.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(FileIssue)))
|
||||
)]
|
||||
#[get("/issue/{issue_id}")]
|
||||
async fn get_issue(
|
||||
pub async fn get_issue(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -252,13 +254,15 @@ async fn get_issue(
|
||||
Ok(web::Json(row.data.0))
|
||||
}
|
||||
|
||||
/// Get info on a specific report for a project.
|
||||
/// Get a project technical report.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(FileReport)))
|
||||
)]
|
||||
#[get("/report/{id}")]
|
||||
async fn get_report(
|
||||
pub async fn get_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -662,13 +666,15 @@ async fn fetch_project_reports(
|
||||
Ok(project_reports)
|
||||
}
|
||||
|
||||
/// Searches all projects which are awaiting technical review.
|
||||
/// Search projects awaiting technical review.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(Vec<SearchResponse>)))
|
||||
responses((status = OK, body = SearchResponse))
|
||||
)]
|
||||
#[post("/search")]
|
||||
async fn search_projects(
|
||||
pub async fn search_projects(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -872,13 +878,15 @@ async fn search_projects(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Gets the technical review report for a specific project.
|
||||
/// Get a project technical review report.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(ProjectReportResponse)))
|
||||
)]
|
||||
#[get("/project/{id}")]
|
||||
async fn get_project_report(
|
||||
pub async fn get_project_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -963,18 +971,20 @@ pub struct SubmitReport {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
/// Submits a verdict for a project based on its technical reports.
|
||||
/// Submit a technical review verdict.
|
||||
///
|
||||
/// Before this is called, all issues for this project's reports must have been
|
||||
/// marked as either safe or unsafe. Otherwise, this will error with
|
||||
/// [`ApiError::TechReviewIssuesWithNoVerdict`], providing the issue IDs which
|
||||
/// are still unmarked.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/submit/{project_id}")]
|
||||
async fn submit_report(
|
||||
pub async fn submit_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -1163,16 +1173,18 @@ pub struct UpdateIssue {
|
||||
pub verdict: DelphiVerdict,
|
||||
}
|
||||
|
||||
/// Updates the state of a technical review issue detail.
|
||||
/// Update technical review issue details.
|
||||
///
|
||||
/// This will not automatically reject the project for malware, but just flag
|
||||
/// this issue with a verdict.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/issue-detail")]
|
||||
async fn update_issue_details(
|
||||
pub async fn update_issue_details(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -1273,11 +1285,15 @@ pub struct AddReport {
|
||||
pub file_id: FileId,
|
||||
}
|
||||
|
||||
/// Adds a file to the technical review queue by adding an empty report, if one
|
||||
/// Add a technical review report.
|
||||
/// does not already exist for it.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = DelphiReportId))
|
||||
)]
|
||||
#[put("/report")]
|
||||
async fn add_report(
|
||||
pub async fn add_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
Reference in New Issue
Block a user