refactor: move web code into own module
parent
412d1e65f1
commit
a6de2c3c14
|
@ -1,18 +1,15 @@
|
||||||
mod api;
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
mod error;
|
mod error;
|
||||||
mod repo;
|
mod repo;
|
||||||
|
mod web;
|
||||||
|
|
||||||
pub use config::{Config, DbConfig, FsConfig};
|
pub use config::{Config, DbConfig, FsConfig};
|
||||||
pub use error::{Result, ServerError};
|
pub use error::{Result, ServerError};
|
||||||
|
|
||||||
use std::{io, path::PathBuf};
|
use std::{io, path::PathBuf};
|
||||||
|
|
||||||
use axum::Router;
|
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use sea_orm_migration::MigratorTrait;
|
use sea_orm_migration::MigratorTrait;
|
||||||
use tokio::runtime;
|
use tokio::runtime;
|
||||||
|
@ -90,12 +87,8 @@ async fn run(global: Global) -> crate::Result<()> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let listener = tokio::net::TcpListener::bind(domain).await?;
|
let listener = tokio::net::TcpListener::bind(domain).await?;
|
||||||
|
|
||||||
// build our application with a single route
|
let app = web::router(global);
|
||||||
let app = Router::new()
|
|
||||||
.nest("/api", crate::api::router())
|
|
||||||
.merge(crate::repo::router(&global.config.api_key))
|
|
||||||
.with_state(global)
|
|
||||||
.layer(TraceLayer::new_for_http());
|
|
||||||
// run it with hyper on localhost:3000
|
// run it with hyper on localhost:3000
|
||||||
Ok(axum::serve(listener, app.into_make_service())
|
Ok(axum::serve(listener, app.into_make_service())
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -5,146 +5,3 @@ pub mod package;
|
||||||
|
|
||||||
pub use actor::{RepoActor, RepoCommand, RepoSharedState};
|
pub use actor::{RepoActor, RepoCommand, RepoSharedState};
|
||||||
pub use handle::Handle;
|
pub use handle::Handle;
|
||||||
|
|
||||||
use crate::FsConfig;
|
|
||||||
|
|
||||||
use axum::{
|
|
||||||
body::Body,
|
|
||||||
extract::{Path, State},
|
|
||||||
http::{Request, StatusCode},
|
|
||||||
response::IntoResponse,
|
|
||||||
routing::{delete, post},
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use futures::TryStreamExt;
|
|
||||||
use tokio_util::io::StreamReader;
|
|
||||||
use tower::util::ServiceExt;
|
|
||||||
use tower_http::{services::ServeFile, validate_request::ValidateRequestHeaderLayer};
|
|
||||||
|
|
||||||
pub fn router(api_key: &str) -> Router<crate::Global> {
|
|
||||||
Router::new()
|
|
||||||
.route(
|
|
||||||
"/:distro/:repo",
|
|
||||||
post(post_package_archive)
|
|
||||||
.delete(delete_repo)
|
|
||||||
.route_layer(ValidateRequestHeaderLayer::bearer(api_key)),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/:distro/:repo/:arch",
|
|
||||||
delete(delete_arch_repo).route_layer(ValidateRequestHeaderLayer::bearer(api_key)),
|
|
||||||
)
|
|
||||||
// Routes added after the layer do not get that layer applied, so the GET requests will not
|
|
||||||
// be authorized
|
|
||||||
.route(
|
|
||||||
"/:distro/:repo/:arch/:filename",
|
|
||||||
delete(delete_package)
|
|
||||||
.route_layer(ValidateRequestHeaderLayer::bearer(api_key))
|
|
||||||
.get(get_file),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serve the package archive files and database archives. If files are requested for an
|
|
||||||
/// architecture that does not have any explicit packages, a repository containing only "any" files
|
|
||||||
/// is returned.
|
|
||||||
async fn get_file(
|
|
||||||
State(global): State<crate::Global>,
|
|
||||||
Path((distro, repo, arch, file_name)): Path<(String, String, String, String)>,
|
|
||||||
req: Request<Body>,
|
|
||||||
) -> crate::Result<impl IntoResponse> {
|
|
||||||
if let Some(repo_id) = global.repo.get_repo(&distro, &repo).await? {
|
|
||||||
match global.config.fs {
|
|
||||||
FsConfig::Local { data_dir } => {
|
|
||||||
let repo_dir = data_dir.join("repos").join(repo_id.to_string());
|
|
||||||
|
|
||||||
let file_name = if file_name == format!("{}.db", repo)
|
|
||||||
|| file_name == format!("{}.db.tar.gz", repo)
|
|
||||||
{
|
|
||||||
format!("{}.db.tar.gz", arch)
|
|
||||||
} else if file_name == format!("{}.files", repo)
|
|
||||||
|| file_name == format!("{}.files.tar.gz", repo)
|
|
||||||
{
|
|
||||||
format!("{}.files.tar.gz", arch)
|
|
||||||
} else {
|
|
||||||
file_name
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = repo_dir.join(file_name);
|
|
||||||
Ok(ServeFile::new(path).oneshot(req).await)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(StatusCode::NOT_FOUND.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn post_package_archive(
|
|
||||||
State(global): State<crate::Global>,
|
|
||||||
Path((distro, repo)): Path<(String, String)>,
|
|
||||||
body: Body,
|
|
||||||
) -> crate::Result<StatusCode> {
|
|
||||||
let repo_id = global.repo.get_or_create_repo(&distro, &repo).await?;
|
|
||||||
|
|
||||||
let [tmp_path] = global.repo.random_file_paths();
|
|
||||||
let mut tmp_file = tokio::fs::File::create(&tmp_path).await?;
|
|
||||||
let mut body = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
|
|
||||||
tokio::io::copy(&mut body, &mut tmp_file).await?;
|
|
||||||
|
|
||||||
global.repo.queue_pkg(repo_id, tmp_path).await;
|
|
||||||
|
|
||||||
Ok(StatusCode::ACCEPTED)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_repo(
|
|
||||||
State(global): State<crate::Global>,
|
|
||||||
Path((distro, repo)): Path<(String, String)>,
|
|
||||||
) -> crate::Result<StatusCode> {
|
|
||||||
if let Some(repo) = global.repo.get_repo(&distro, &repo).await? {
|
|
||||||
global.repo.remove_repo(repo).await?;
|
|
||||||
|
|
||||||
tracing::info!("Removed repository {repo}");
|
|
||||||
|
|
||||||
Ok(StatusCode::OK)
|
|
||||||
} else {
|
|
||||||
Ok(StatusCode::NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_arch_repo(
|
|
||||||
State(global): State<crate::Global>,
|
|
||||||
Path((distro, repo, arch)): Path<(String, String, String)>,
|
|
||||||
) -> crate::Result<StatusCode> {
|
|
||||||
if let Some(repo) = global.repo.get_repo(&distro, &repo).await? {
|
|
||||||
global.repo.remove_repo_arch(repo, &arch).await?;
|
|
||||||
|
|
||||||
tracing::info!("Removed architecture '{arch}' from repository {repo}");
|
|
||||||
|
|
||||||
Ok(StatusCode::OK)
|
|
||||||
} else {
|
|
||||||
Ok(StatusCode::NOT_FOUND)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_package(
|
|
||||||
State(global): State<crate::Global>,
|
|
||||||
Path((distro, repo, arch, pkg_name)): Path<(String, String, String, String)>,
|
|
||||||
) -> crate::Result<StatusCode> {
|
|
||||||
Ok(StatusCode::NOT_FOUND)
|
|
||||||
//if let Some(mgr) = global.mgr.get_mgr(&distro).await {
|
|
||||||
// let pkg_removed = mgr.remove_pkg(&repo, &arch, &pkg_name).await?;
|
|
||||||
//
|
|
||||||
// if pkg_removed {
|
|
||||||
// tracing::info!(
|
|
||||||
// "Removed package '{}' ({}) from repository '{}'",
|
|
||||||
// pkg_name,
|
|
||||||
// arch,
|
|
||||||
// repo
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// Ok(StatusCode::OK)
|
|
||||||
// } else {
|
|
||||||
// Ok(StatusCode::NOT_FOUND)
|
|
||||||
// }
|
|
||||||
//} else {
|
|
||||||
// Ok(StatusCode::NOT_FOUND)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
mod api;
|
||||||
|
mod repo;
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
|
pub fn router(global: crate::Global) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.nest("/api", api::router())
|
||||||
|
.merge(repo::router(&global.config.api_key))
|
||||||
|
.with_state(global)
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
use crate::FsConfig;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
extract::{Path, State},
|
||||||
|
http::{Request, StatusCode},
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{delete, post},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use tokio_util::io::StreamReader;
|
||||||
|
use tower::util::ServiceExt;
|
||||||
|
use tower_http::{services::ServeFile, validate_request::ValidateRequestHeaderLayer};
|
||||||
|
|
||||||
|
pub fn router(api_key: &str) -> Router<crate::Global> {
|
||||||
|
Router::new()
|
||||||
|
.route(
|
||||||
|
"/:distro/:repo",
|
||||||
|
post(post_package_archive)
|
||||||
|
.delete(delete_repo)
|
||||||
|
.route_layer(ValidateRequestHeaderLayer::bearer(api_key)),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/:distro/:repo/:arch",
|
||||||
|
delete(delete_arch_repo).route_layer(ValidateRequestHeaderLayer::bearer(api_key)),
|
||||||
|
)
|
||||||
|
// Routes added after the layer do not get that layer applied, so the GET requests will not
|
||||||
|
// be authorized
|
||||||
|
.route(
|
||||||
|
"/:distro/:repo/:arch/:filename",
|
||||||
|
delete(delete_package)
|
||||||
|
.route_layer(ValidateRequestHeaderLayer::bearer(api_key))
|
||||||
|
.get(get_file),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serve the package archive files and database archives. If files are requested for an
|
||||||
|
/// architecture that does not have any explicit packages, a repository containing only "any" files
|
||||||
|
/// is returned.
|
||||||
|
async fn get_file(
|
||||||
|
State(global): State<crate::Global>,
|
||||||
|
Path((distro, repo, arch, file_name)): Path<(String, String, String, String)>,
|
||||||
|
req: Request<Body>,
|
||||||
|
) -> crate::Result<impl IntoResponse> {
|
||||||
|
if let Some(repo_id) = global.repo.get_repo(&distro, &repo).await? {
|
||||||
|
match global.config.fs {
|
||||||
|
FsConfig::Local { data_dir } => {
|
||||||
|
let repo_dir = data_dir.join("repos").join(repo_id.to_string());
|
||||||
|
|
||||||
|
let file_name = if file_name == format!("{}.db", repo)
|
||||||
|
|| file_name == format!("{}.db.tar.gz", repo)
|
||||||
|
{
|
||||||
|
format!("{}.db.tar.gz", arch)
|
||||||
|
} else if file_name == format!("{}.files", repo)
|
||||||
|
|| file_name == format!("{}.files.tar.gz", repo)
|
||||||
|
{
|
||||||
|
format!("{}.files.tar.gz", arch)
|
||||||
|
} else {
|
||||||
|
file_name
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = repo_dir.join(file_name);
|
||||||
|
Ok(ServeFile::new(path).oneshot(req).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(StatusCode::NOT_FOUND.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_package_archive(
|
||||||
|
State(global): State<crate::Global>,
|
||||||
|
Path((distro, repo)): Path<(String, String)>,
|
||||||
|
body: Body,
|
||||||
|
) -> crate::Result<StatusCode> {
|
||||||
|
let repo_id = global.repo.get_or_create_repo(&distro, &repo).await?;
|
||||||
|
|
||||||
|
let [tmp_path] = global.repo.random_file_paths();
|
||||||
|
let mut tmp_file = tokio::fs::File::create(&tmp_path).await?;
|
||||||
|
let mut body = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
|
||||||
|
tokio::io::copy(&mut body, &mut tmp_file).await?;
|
||||||
|
|
||||||
|
global.repo.queue_pkg(repo_id, tmp_path).await;
|
||||||
|
|
||||||
|
Ok(StatusCode::ACCEPTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_repo(
|
||||||
|
State(global): State<crate::Global>,
|
||||||
|
Path((distro, repo)): Path<(String, String)>,
|
||||||
|
) -> crate::Result<StatusCode> {
|
||||||
|
if let Some(repo) = global.repo.get_repo(&distro, &repo).await? {
|
||||||
|
global.repo.remove_repo(repo).await?;
|
||||||
|
|
||||||
|
tracing::info!("Removed repository {repo}");
|
||||||
|
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
} else {
|
||||||
|
Ok(StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_arch_repo(
|
||||||
|
State(global): State<crate::Global>,
|
||||||
|
Path((distro, repo, arch)): Path<(String, String, String)>,
|
||||||
|
) -> crate::Result<StatusCode> {
|
||||||
|
if let Some(repo) = global.repo.get_repo(&distro, &repo).await? {
|
||||||
|
global.repo.remove_repo_arch(repo, &arch).await?;
|
||||||
|
|
||||||
|
tracing::info!("Removed architecture '{arch}' from repository {repo}");
|
||||||
|
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
} else {
|
||||||
|
Ok(StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_package(
|
||||||
|
State(global): State<crate::Global>,
|
||||||
|
Path((distro, repo, arch, pkg_name)): Path<(String, String, String, String)>,
|
||||||
|
) -> crate::Result<StatusCode> {
|
||||||
|
Ok(StatusCode::NOT_FOUND)
|
||||||
|
//if let Some(mgr) = global.mgr.get_mgr(&distro).await {
|
||||||
|
// let pkg_removed = mgr.remove_pkg(&repo, &arch, &pkg_name).await?;
|
||||||
|
//
|
||||||
|
// if pkg_removed {
|
||||||
|
// tracing::info!(
|
||||||
|
// "Removed package '{}' ({}) from repository '{}'",
|
||||||
|
// pkg_name,
|
||||||
|
// arch,
|
||||||
|
// repo
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// Ok(StatusCode::OK)
|
||||||
|
// } else {
|
||||||
|
// Ok(StatusCode::NOT_FOUND)
|
||||||
|
// }
|
||||||
|
//} else {
|
||||||
|
// Ok(StatusCode::NOT_FOUND)
|
||||||
|
//}
|
||||||
|
}
|
Loading…
Reference in New Issue