#![allow(dead_code)]
use axum::Router;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
use crate::activity_log::ActivityEntry;
use crate::auth::{TokenListItem, TokenListResponse};
use crate::health::StorageHealth;
use crate::ui::api::{DashboardResponse, GlobalStats, MountPoint, RegistryCardStats};
use crate::AppState;
#[derive(OpenApi)]
#[openapi(
info(
title = "Nora",
version = "1.2.0",
description = "Multi-protocol package registry supporting Docker, Maven, npm, Cargo, PyPI, Go, Raw, RubyGems, Terraform, Ansible, NuGet, pub.dev, Conan, RPM, and Debian",
license(name = "MIT"),
contact(name = "The NORA Authors", url = "https://getnora.dev")
),
servers(
(url = "/", description = "Current server")
),
tags(
(name = "health", description = "Health check endpoints"),
(name = "metrics", description = "Prometheus metrics"),
(name = "dashboard", description = "Dashboard & Metrics API"),
(name = "docker", description = "Docker Registry v2 API"),
(name = "maven", description = "Maven Repository API"),
(name = "npm", description = "npm Registry API"),
(name = "cargo", description = "Cargo Registry API"),
(name = "pypi", description = "PyPI Simple API"),
(name = "go", description = "Go Module Proxy API"),
(name = "raw", description = "Raw File Storage API"),
(name = "gems", description = "RubyGems Proxy API"),
(name = "terraform", description = "Terraform Registry Proxy API"),
(name = "ansible", description = "Ansible Galaxy Proxy API"),
(name = "nuget", description = "NuGet v3 Registry Proxy API"),
(name = "pub", description = "Dart/Flutter Pub Registry Proxy API"),
(name = "conan", description = "Conan V2 Registry Proxy API (C/C++)"),
(name = "rpm", description = "RPM (yum/dnf) Hosted & Pull-Through Repository API"),
(name = "deb", description = "Debian (APT) Hosted & Pull-Through Repository API"),
(name = "auth", description = "Authentication & API Tokens")
),
paths(
// Health
crate::openapi::health_check,
crate::openapi::readiness_check,
// Metrics
crate::openapi::prometheus_metrics,
// Dashboard
crate::openapi::dashboard_metrics,
// Docker - Read
crate::openapi::docker_version,
crate::openapi::docker_catalog,
crate::openapi::docker_tags,
crate::openapi::docker_manifest_get,
crate::openapi::docker_blob_head,
crate::openapi::docker_blob_get,
// Docker - Write
crate::openapi::docker_manifest_put,
crate::openapi::docker_manifest_delete,
crate::openapi::docker_blob_upload_start,
crate::openapi::docker_blob_upload_patch,
crate::openapi::docker_blob_upload_put,
// Maven
crate::openapi::maven_artifact_get,
crate::openapi::maven_artifact_put,
// npm
crate::openapi::npm_package,
crate::openapi::npm_publish,
// Cargo
crate::openapi::cargo_index_config,
crate::openapi::cargo_sparse_index,
crate::openapi::cargo_metadata,
crate::openapi::cargo_download,
crate::openapi::cargo_publish,
// PyPI
crate::openapi::pypi_simple,
crate::openapi::pypi_package,
crate::openapi::pypi_upload,
// Go
crate::openapi::go_module_latest,
crate::openapi::go_module_info,
crate::openapi::go_module_mod,
crate::openapi::go_module_zip,
// Raw
crate::openapi::raw_file_get,
crate::openapi::raw_file_put,
// RubyGems
crate::openapi::gems_info,
crate::openapi::gems_download,
// Terraform
crate::openapi::terraform_service_discovery,
crate::openapi::terraform_provider_versions,
// Ansible Galaxy
crate::openapi::ansible_collection_list,
crate::openapi::ansible_download,
// NuGet
crate::openapi::nuget_service_index,
crate::openapi::nuget_download,
// Pub (Dart/Flutter)
crate::openapi::pub_package_list,
crate::openapi::pub_archive_download,
// Conan (C/C++)
crate::openapi::conan_ping,
crate::openapi::conan_recipe_file,
// RPM (yum/dnf)
crate::openapi::rpm_repomd,
crate::openapi::rpm_upload,
crate::openapi::rpm_download,
crate::openapi::rpm_delete,
crate::openapi::rpm_pubkey,
crate::openapi::rpm_reindex,
// Debian (APT)
crate::openapi::deb_release,
crate::openapi::deb_dist_release,
crate::openapi::deb_upload,
crate::openapi::deb_download,
crate::openapi::deb_delete,
crate::openapi::deb_pubkey,
crate::openapi::deb_reindex,
// Tokens
crate::openapi::create_token,
crate::openapi::list_tokens,
crate::openapi::revoke_token,
// Admin
crate::openapi::admin_reindex,
),
components(
schemas(
HealthResponse,
StorageHealth,
RegistriesHealth,
UpstreamHealthSchema,
DashboardResponse,
GlobalStats,
RegistryCardStats,
MountPoint,
ActivityEntry,
DockerVersion,
DockerCatalog,
DockerTags,
TokenRequest,
TokenResponse,
TokenListResponse,
TokenListItem,
ErrorResponse
)
)
)]
pub struct ApiDoc;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Serialize, ToSchema)]
pub struct HealthResponse {
pub status: String,
pub version: String,
pub uptime_seconds: u64,
pub storage: StorageHealth,
pub registries: RegistriesHealth,
pub upstreams: std::collections::HashMap<String, UpstreamHealthSchema>,
}
#[derive(Serialize, ToSchema)]
pub struct UpstreamHealthSchema {
pub status: String,
pub failure_count: u32,
pub last_failure_seconds_ago: Option<u64>,
}
#[derive(Serialize, ToSchema)]
pub struct RegistriesHealth {
pub docker: String,
pub maven: String,
pub npm: String,
pub cargo: String,
pub pypi: String,
pub go: String,
pub raw: String,
}
#[derive(Serialize, ToSchema)]
pub struct DockerVersion {
#[serde(rename = "Docker-Distribution-API-Version")]
pub version: String,
}
#[derive(Serialize, ToSchema)]
pub struct DockerCatalog {
pub repositories: Vec<String>,
}
#[derive(Serialize, ToSchema)]
pub struct DockerTags {
pub name: String,
pub tags: Vec<String>,
}
#[derive(Deserialize, ToSchema)]
pub struct TokenRequest {
pub username: String,
pub password: String,
#[serde(default = "default_ttl")]
pub ttl_days: u32,
pub description: Option<String>,
}
fn default_ttl() -> u32 {
30
}
#[derive(Serialize, ToSchema)]
pub struct TokenResponse {
pub token: String,
pub expires_in_days: u32,
}
#[derive(Serialize, ToSchema)]
pub struct ErrorResponse {
pub error: String,
}
#[utoipa::path(
get,
path = "/health",
tag = "health",
responses(
(status = 200, description = "Process is up; `status` field reports `healthy` or `degraded` (storage unreachable)", body = HealthResponse)
)
)]
pub async fn health_check() {}
#[utoipa::path(
get,
path = "/ready",
tag = "health",
responses(
(status = 200, description = "Service is ready"),
(status = 503, description = "Service is not ready")
)
)]
pub async fn readiness_check() {}
#[utoipa::path(
get,
path = "/metrics",
tag = "metrics",
responses(
(status = 200, description = "Prometheus metrics", content_type = "text/plain")
)
)]
pub async fn prometheus_metrics() {}
#[utoipa::path(
get,
path = "/api/ui/dashboard",
tag = "dashboard",
responses(
(status = 200, description = "Dashboard metrics", body = DashboardResponse)
)
)]
pub async fn dashboard_metrics() {}
#[utoipa::path(
get,
path = "/v2/",
tag = "docker",
responses(
(status = 200, description = "Registry is available", body = DockerVersion),
(status = 400, description = "Invalid request", body = ErrorResponse),
(status = 401, description = "Authentication required"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_version() {}
#[utoipa::path(
get,
path = "/v2/_catalog",
tag = "docker",
responses(
(status = 200, description = "Repository list", body = DockerCatalog),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_catalog() {}
#[utoipa::path(
get,
path = "/v2/{name}/tags/list",
tag = "docker",
params(
("name" = String, Path, description = "Repository name (e.g., 'alpine' or 'library/nginx')")
),
responses(
(status = 200, description = "Tag list", body = DockerTags),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Repository not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_tags() {}
#[utoipa::path(
get,
path = "/v2/{name}/manifests/{reference}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("reference" = String, Path, description = "Tag or digest (sha256:...)")
),
responses(
(status = 200, description = "Manifest content"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Manifest not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_manifest_get() {}
#[utoipa::path(
head,
path = "/v2/{name}/blobs/{digest}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("digest" = String, Path, description = "Blob digest (sha256:...)")
),
responses(
(status = 200, description = "Blob exists, Content-Length header contains size"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Blob not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_blob_head() {}
#[utoipa::path(
get,
path = "/v2/{name}/blobs/{digest}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("digest" = String, Path, description = "Blob digest (sha256:...)")
),
responses(
(status = 200, description = "Blob content"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Blob not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_blob_get() {}
#[utoipa::path(
put,
path = "/v2/{name}/manifests/{reference}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("reference" = String, Path, description = "Tag or digest")
),
responses(
(status = 201, description = "Manifest created, Docker-Content-Digest header contains digest"),
(status = 400, description = "Invalid manifest"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_manifest_put() {}
#[utoipa::path(
delete,
path = "/v2/{name}/manifests/{reference}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("reference" = String, Path, description = "Tag or digest (sha256:...)")
),
responses(
(status = 202, description = "Manifest deleted"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Manifest not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_manifest_delete() {}
#[utoipa::path(
post,
path = "/v2/{name}/blobs/uploads/",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("mount" = Option<String>, Query, description = "Digest of an existing blob to mount (sha256:...)"),
("from" = Option<String>, Query, description = "Repository that holds the blob to mount")
),
responses(
(status = 201, description = "Blob mounted, Location header contains the blob URL"),
(status = 202, description = "Upload started, Location header contains upload URL"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_blob_upload_start() {}
#[utoipa::path(
patch,
path = "/v2/{name}/blobs/uploads/{uuid}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("uuid" = String, Path, description = "Upload session UUID")
),
responses(
(status = 202, description = "Chunk accepted, Range header indicates bytes received"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_blob_upload_patch() {}
#[utoipa::path(
put,
path = "/v2/{name}/blobs/uploads/{uuid}",
tag = "docker",
params(
("name" = String, Path, description = "Repository name"),
("uuid" = String, Path, description = "Upload session UUID"),
("digest" = String, Query, description = "Expected blob digest (sha256:...)")
),
responses(
(status = 201, description = "Blob created"),
(status = 400, description = "Digest mismatch or missing"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn docker_blob_upload_put() {}
#[utoipa::path(
get,
path = "/maven2/{path}",
tag = "maven",
params(
("path" = String, Path, description = "Artifact path (e.g., org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar)")
),
responses(
(status = 200, description = "Artifact content"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Artifact not found, trying upstream proxies"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn maven_artifact_get() {}
#[utoipa::path(
put,
path = "/maven2/{path}",
tag = "maven",
params(
("path" = String, Path, description = "Artifact path")
),
responses(
(status = 201, description = "Artifact uploaded"),
(status = 400, description = "Invalid path (non-ASCII characters)"),
(status = 409, description = "Version already exists (immutable releases)"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time"),
(status = 500, description = "Storage error")
)
)]
pub async fn maven_artifact_put() {}
#[utoipa::path(
get,
path = "/npm/{name}",
tag = "npm",
params(
("name" = String, Path, description = "Package name (e.g., 'lodash' or '@scope/package')")
),
responses(
(status = 200, description = "Package metadata (JSON)"),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn npm_package() {}
#[utoipa::path(
put,
path = "/npm/{name}",
tag = "npm",
params(
("name" = String, Path, description = "Package name (e.g., 'lodash' or '@scope/package')")
),
responses(
(status = 200, description = "Package published"),
(status = 409, description = "Version already exists"),
(status = 400, description = "Invalid package data"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn npm_publish() {}
#[utoipa::path(
get,
path = "/cargo/index/config.json",
tag = "cargo",
responses(
(status = 200, description = "Sparse index configuration (JSON)"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn cargo_index_config() {}
#[utoipa::path(
get,
path = "/cargo/index/{path}",
tag = "cargo",
params(
("path" = String, Path, description = "Sparse index path (e.g., 'se/rd/serde')")
),
responses(
(status = 200, description = "Crate index entries (one JSON per line)"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Crate not found in index"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn cargo_sparse_index() {}
#[utoipa::path(
get,
path = "/cargo/api/v1/crates/{crate_name}",
tag = "cargo",
params(
("crate_name" = String, Path, description = "Crate name")
),
responses(
(status = 200, description = "Crate metadata (JSON)"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Crate not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn cargo_metadata() {}
#[utoipa::path(
get,
path = "/cargo/api/v1/crates/{crate_name}/{version}/download",
tag = "cargo",
params(
("crate_name" = String, Path, description = "Crate name"),
("version" = String, Path, description = "Crate version")
),
responses(
(status = 200, description = "Crate file (.crate)"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Crate version not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn cargo_download() {}
#[utoipa::path(
put,
path = "/cargo/api/v1/crates/new",
tag = "cargo",
responses(
(status = 200, description = "Crate published"),
(status = 409, description = "Version already exists"),
(status = 400, description = "Invalid crate data"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn cargo_publish() {}
#[utoipa::path(
get,
path = "/simple/",
tag = "pypi",
responses(
(status = 200, description = "HTML list of packages"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn pypi_simple() {}
#[utoipa::path(
get,
path = "/simple/{name}/",
tag = "pypi",
params(
("name" = String, Path, description = "Package name")
),
responses(
(status = 200, description = "HTML list of package files"),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn pypi_package() {}
#[utoipa::path(
post,
path = "/simple/",
tag = "pypi",
responses(
(status = 200, description = "Package uploaded"),
(status = 409, description = "Version already exists"),
(status = 400, description = "Invalid upload data"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn pypi_upload() {}
#[utoipa::path(
get,
path = "/go/{module}/@latest",
tag = "go",
params(
("module" = String, Path, description = "Module path (e.g., 'golang.org/x/text')")
),
responses(
(status = 200, description = "Latest version info (JSON)"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Module not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn go_module_latest() {}
#[utoipa::path(
get,
path = "/go/{module}/@v/{version}.info",
tag = "go",
params(
("module" = String, Path, description = "Module path"),
("version" = String, Path, description = "Module version (e.g., 'v1.14.0')")
),
responses(
(status = 200, description = "Version info (JSON)"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Version not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn go_module_info() {}
#[utoipa::path(
get,
path = "/go/{module}/@v/{version}.mod",
tag = "go",
params(
("module" = String, Path, description = "Module path"),
("version" = String, Path, description = "Module version")
),
responses(
(status = 200, description = "go.mod file content"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Version not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn go_module_mod() {}
#[utoipa::path(
get,
path = "/go/{module}/@v/{version}.zip",
tag = "go",
params(
("module" = String, Path, description = "Module path"),
("version" = String, Path, description = "Module version")
),
responses(
(status = 200, description = "Module zip archive"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Version not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn go_module_zip() {}
#[utoipa::path(
get,
path = "/raw/{path}",
tag = "raw",
params(
("path" = String, Path, description = "File path (e.g., 'myproject/config.yaml')")
),
responses(
(status = 200, description = "File content"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "File not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn raw_file_get() {}
#[utoipa::path(
put,
path = "/raw/{path}",
tag = "raw",
params(
("path" = String, Path, description = "File path")
),
responses(
(status = 200, description = "File overwritten (conditional PUT with If-Match)"),
(status = 201, description = "File uploaded"),
(status = 400, description = "Invalid path (non-ASCII characters)"),
(status = 409, description = "File already exists (immutable)"),
(status = 412, description = "Precondition failed (ETag mismatch or resource state)"),
(status = 413, description = "File too large"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time"),
(status = 500, description = "Storage error")
)
)]
pub async fn raw_file_put() {}
#[utoipa::path(
get,
path = "/gems/info/{name}",
tag = "gems",
params(
("name" = String, Path, description = "Gem name (e.g., 'rails')")
),
responses(
(status = 200, description = "Compact index for gem"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Gem not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn gems_info() {}
#[utoipa::path(
get,
path = "/gems/gems/{filename}",
tag = "gems",
params(
("filename" = String, Path, description = "Gem file (e.g., 'rails-7.0.0.gem')")
),
responses(
(status = 200, description = "Gem binary"),
(status = 404, description = "Gem not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn gems_download() {}
#[utoipa::path(
get,
path = "/terraform/.well-known/terraform.json",
tag = "terraform",
responses(
(status = 200, description = "Service discovery JSON"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn terraform_service_discovery() {}
#[utoipa::path(
get,
path = "/terraform/v1/providers/{ns}/{ptype}/versions",
tag = "terraform",
params(
("ns" = String, Path, description = "Provider namespace"),
("ptype" = String, Path, description = "Provider type")
),
responses(
(status = 200, description = "Version list"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Provider not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn terraform_provider_versions() {}
#[utoipa::path(
get,
path = "/ansible/api/v3/plugin/ansible/content/published/collections/index/",
tag = "ansible",
responses(
(status = 200, description = "Collection list"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time"),
(status = 502, description = "Upstream unreachable")
)
)]
pub async fn ansible_collection_list() {}
#[utoipa::path(
get,
path = "/ansible/download/{filename}",
tag = "ansible",
params(
("filename" = String, Path, description = "Collection tarball (e.g., 'community-general-7.0.0.tar.gz')")
),
responses(
(status = 200, description = "Collection tarball"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Collection not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn ansible_download() {}
#[utoipa::path(
get,
path = "/nuget/v3/index.json",
tag = "nuget",
responses(
(status = 200, description = "Service index JSON with @id URLs rewritten"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn nuget_service_index() {}
#[utoipa::path(
get,
path = "/nuget/v3/flatcontainer/{path}",
tag = "nuget",
params(
("path" = String, Path, description = "Package path (e.g., 'newtonsoft.json/13.0.1/newtonsoft.json.13.0.1.nupkg')")
),
responses(
(status = 200, description = "Package file (.nupkg or .nuspec)"),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn nuget_download() {}
#[utoipa::path(
get,
path = "/pub/api/packages/{package}",
tag = "pub",
params(
("package" = String, Path, description = "Package name")
),
responses(
(status = 200, description = "Package metadata with versions"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn pub_package_list() {}
#[utoipa::path(
get,
path = "/pub/packages/{package}/versions/{archive}",
tag = "pub",
params(
("package" = String, Path, description = "Package name"),
("archive" = String, Path, description = "Version archive (e.g., '1.2.0.tar.gz')")
),
responses(
(status = 200, description = "Package archive (.tar.gz)"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn pub_archive_download() {}
#[utoipa::path(
get,
path = "/conan/v2/ping",
tag = "conan",
responses(
(status = 200, description = "Ping response with X-Conan-Server-Capabilities header"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn conan_ping() {}
#[utoipa::path(
get,
path = "/conan/v2/conans/{name}/{ver}/{user}/{chan}/revisions/{rrev}/files/{filename}",
tag = "conan",
params(
("name" = String, Path, description = "Package name"),
("ver" = String, Path, description = "Package version"),
("user" = String, Path, description = "User (or _ for default)"),
("chan" = String, Path, description = "Channel (or _ for default)"),
("rrev" = String, Path, description = "Recipe revision hash"),
("filename" = String, Path, description = "File name (e.g., conanfile.py)")
),
responses(
(status = 200, description = "File content"),
(status = 400, description = "Invalid input", body = ErrorResponse),
(status = 404, description = "File not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn conan_recipe_file() {}
#[utoipa::path(
get,
path = "/rpm/{repo}/repodata/repomd.xml",
tag = "rpm",
params(
("repo" = String, Path, description = "Repository name")
),
responses(
(status = 200, description = "repomd.xml referencing primary/filelists/other metadata"),
(status = 404, description = "Repository not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn rpm_repomd() {}
#[utoipa::path(
put,
path = "/rpm/{repo}/{path}",
tag = "rpm",
params(
("repo" = String, Path, description = "Repository name"),
("path" = String, Path, description = "Package path, must end in .rpm")
),
responses(
(status = 201, description = "Package stored and repodata regenerated"),
(status = 400, description = "Invalid path or not a valid RPM", body = ErrorResponse),
(status = 413, description = "File exceeds rpm.max_file_size"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn rpm_upload() {}
#[utoipa::path(
get,
path = "/rpm/{repo}/{path}",
tag = "rpm",
params(
("repo" = String, Path, description = "Repository name"),
("path" = String, Path, description = "Package or repodata path")
),
responses(
(status = 200, description = "File content"),
(status = 404, description = "File not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn rpm_download() {}
#[utoipa::path(
delete,
path = "/rpm/{repo}/{path}",
tag = "rpm",
params(
("repo" = String, Path, description = "Repository name"),
("path" = String, Path, description = "Package path, must end in .rpm")
),
responses(
(status = 204, description = "Package deleted and repodata regenerated"),
(status = 400, description = "Invalid path", body = ErrorResponse),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn rpm_delete() {}
#[utoipa::path(
get,
path = "/rpm/{repo}/repodata/repomd.xml.key",
tag = "rpm",
params(
("repo" = String, Path, description = "Repository name")
),
responses(
(status = 200, description = "Armored OpenPGP public key"),
(status = 404, description = "Index signing is disabled"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn rpm_pubkey() {}
#[utoipa::path(
post,
path = "/rpm/{repo}/-/reindex",
tag = "rpm",
params(
("repo" = String, Path, description = "Repository name")
),
responses(
(status = 200, description = "Reconciled; JSON counts of packages, adopted sidecars, removed orphans"),
(status = 404, description = "No such repository"),
(status = 422, description = "A stored file is not a valid package"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn rpm_reindex() {}
#[utoipa::path(
get,
path = "/deb/{repo}/Release",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name")
),
responses(
(status = 200, description = "Release file with Packages checksums"),
(status = 404, description = "Repository not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_release() {}
#[utoipa::path(
get,
path = "/deb/{repo}/dists/{distribution}/Release",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name"),
("distribution" = String, Path, description = "Distribution (suite) name")
),
responses(
(status = 200, description = "Release file listing per-component/arch Packages checksums"),
(status = 404, description = "Distribution not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_dist_release() {}
#[utoipa::path(
put,
path = "/deb/{repo}/{path}",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name"),
("path" = String, Path, description = "Package path, must end in .deb"),
("distribution" = Option<String>, Query, description = "Publish into the structured layout under dists/{distribution}/ (omit for a flat repository)"),
("component" = Option<String>, Query, description = "Component within the distribution (default: main; requires distribution)")
),
responses(
(status = 201, description = "Package stored and indexes regenerated"),
(status = 400, description = "Invalid path, placement, or not a valid deb", body = ErrorResponse),
(status = 413, description = "File exceeds deb.max_file_size"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_upload() {}
#[utoipa::path(
get,
path = "/deb/{repo}/{path}",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name"),
("path" = String, Path, description = "Package or index path")
),
responses(
(status = 200, description = "File content"),
(status = 404, description = "File not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_download() {}
#[utoipa::path(
delete,
path = "/deb/{repo}/{path}",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name"),
("path" = String, Path, description = "Package path, must end in .deb")
),
responses(
(status = 204, description = "Package deleted and indexes regenerated"),
(status = 400, description = "Invalid path", body = ErrorResponse),
(status = 404, description = "Package not found"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_delete() {}
#[utoipa::path(
get,
path = "/deb/{repo}/pubkey.gpg",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name")
),
responses(
(status = 200, description = "Armored OpenPGP public key"),
(status = 404, description = "Index signing is disabled"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_pubkey() {}
#[utoipa::path(
post,
path = "/deb/{repo}/-/reindex",
tag = "deb",
params(
("repo" = String, Path, description = "Repository name")
),
responses(
(status = 200, description = "Reconciled; JSON counts of packages, adopted sidecars, removed orphans"),
(status = 404, description = "No such repository"),
(status = 422, description = "A stored file is not a valid package"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn deb_reindex() {}
#[utoipa::path(
post,
path = "/api/tokens",
tag = "auth",
request_body = TokenRequest,
responses(
(status = 200, description = "Token created", body = TokenResponse),
(status = 401, description = "Invalid credentials", body = ErrorResponse),
(status = 422, description = "Missing required fields", body = ErrorResponse),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time"),
(status = 503, description = "Auth not configured", body = ErrorResponse)
)
)]
pub async fn create_token() {}
#[utoipa::path(
post,
path = "/api/tokens/list",
tag = "auth",
request_body = TokenRequest,
responses(
(status = 200, description = "Token list", body = TokenListResponse),
(status = 401, description = "Invalid credentials", body = ErrorResponse),
(status = 422, description = "Invalid request body", body = ErrorResponse),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn list_tokens() {}
#[utoipa::path(
post,
path = "/api/tokens/revoke",
tag = "auth",
responses(
(status = 200, description = "Token revoked"),
(status = 401, description = "Invalid credentials", body = ErrorResponse),
(status = 404, description = "Token not found", body = ErrorResponse),
(status = 415, description = "Unsupported Content-Type"),
(status = 429, description = "Rate limit exceeded. Retry-After header indicates wait time")
)
)]
pub async fn revoke_token() {}
#[utoipa::path(
post,
path = "/api/v1/admin/reindex",
tag = "admin",
params(
("registry" = Option<String>, Query, description = "Single registry to reindex; all if omitted")
),
responses(
(status = 202, description = "Reindex accepted; rebuild runs in background"),
(status = 400, description = "Unknown registry name"),
(status = 401, description = "Authentication required"),
(status = 403, description = "Admin role required"),
(status = 429, description = "Reindex debounced. Retry-After header indicates wait time")
)
)]
pub async fn admin_reindex() {}
pub fn routes() -> Router<AppState> {
Router::new()
.merge(SwaggerUi::new("/api-docs").url("/api-docs/openapi.json", ApiDoc::openapi()))
}