diff --git a/src/main.rs b/src/main.rs index 8b77290..fdeaf20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,10 +11,10 @@ mod statics; use crate::{ data::FilePath, error::Result, - service::{ApiResponse, Bitbucket, Github, Service}, + service::{Bitbucket, GitLab, Github, Service}, }; use actix_web::{ - http::header::{self, CacheControl, CacheDirective, Expires, LOCATION}, + http::header::{self, CacheControl, CacheDirective, Expires}, middleware, web, App, Error, HttpResponse, HttpServer, }; use awc::{http::StatusCode, Client}; @@ -64,30 +64,7 @@ fn redirect( .header(header::USER_AGENT, statics::USER_AGENT.as_str()) .send() .from_err() - .and_then(move |mut response| match response.status() { - StatusCode::OK => Box::new( - response - .json::() - .map(move |resp| { - HttpResponse::SeeOther() - .header( - LOCATION, - T::redirect_url( - &data.user, - &data.repo, - resp.commit_ref(), - &data.file, - ) - .as_str(), - ) - .finish() - }) - .from_err(), - ) - as Box>, - code => Box::new(futures::future::ok(HttpResponse::build(code).finish())) - as Box>, - }), + .and_then(move |response| T::request_head(response, data, client)), ) } @@ -104,7 +81,7 @@ fn handle_request( fn main() -> Result<()> { std::env::set_var("RUST_LOG", "actix_server=info,actix_web=trace"); - env_logger::init(); + pretty_env_logger::init(); openssl_probe::init_ssl_cert_env_vars(); Ok(HttpServer::new(move || { @@ -119,9 +96,11 @@ fn main() -> Result<()> { "/bitbucket/{user}/{repo}/{commit}/{file:.*}", web::get().to_async(handle_request::), ) - // .default_service(web::resource("").route(web::get().to_async(p404))) + .route( + "/gitlab/{user}/{repo}/{commit}/{file:.*}", + web::get().to_async(handle_request::), + ) }) - // .workers(OPT.workers) .bind("0.0.0.0:8080")? .run()?) } diff --git a/src/service.rs b/src/service.rs index 0bf53cb..cf57e5a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,11 @@ use crate::data::FilePath; +use actix_web::{ + http::{header::LOCATION, StatusCode}, + web, Error, HttpResponse, +}; +use awc::{error::PayloadError, Client, ClientResponse}; +use bytes::Bytes; +use futures::{Future, Stream}; pub(crate) trait ApiResponse { fn commit_ref(&self) -> &str; @@ -21,8 +28,8 @@ pub(crate) struct BitbucketApiResponse { } #[derive(Deserialize)] -pub(crate) struct BitbucketEntry { - pub(crate) hash: String, +struct BitbucketEntry { + hash: String, } impl ApiResponse for BitbucketApiResponse { @@ -31,11 +38,64 @@ impl ApiResponse for BitbucketApiResponse { } } +#[derive(Deserialize)] +struct GitLabProject { + id: u64, +} + +#[derive(Deserialize)] +pub(crate) struct GitLabApiResponse { + commit: GitLabCommit, +} + +#[derive(Deserialize)] +struct GitLabCommit { + id: String, +} + +impl ApiResponse for GitLabApiResponse { + fn commit_ref(&self) -> &str { + &self.commit.id + } +} + pub(crate) trait Service { type Response: for<'de> serde::Deserialize<'de> + ApiResponse + 'static; fn raw_url(user: &str, repo: &str, commit: &str, file: &str) -> String; fn api_url(path: &FilePath) -> String; fn redirect_url(user: &str, repo: &str, commit: &str, file: &str) -> String; + fn request_head( + mut response: ClientResponse, + data: web::Path, + _client: web::Data, + ) -> Box> + where + S: 'static + Stream, + { + Box::new(match response.status() { + StatusCode::OK => Box::new( + response + .json::() + .map(move |resp| { + HttpResponse::SeeOther() + .header( + LOCATION, + Self::redirect_url( + &data.user, + &data.repo, + resp.commit_ref(), + &data.file, + ) + .as_str(), + ) + .finish() + }) + .from_err(), + ) as Box>, + code => Box::new(futures::future::ok(HttpResponse::build(code).finish())) + as Box>, + }) + } } pub(crate) struct Github; @@ -85,3 +145,86 @@ impl Service for Bitbucket { format!("/bitbucket/{}/{}/{}/{}", user, repo, commit, file) } } + +pub(crate) struct GitLab; + +impl Service for GitLab { + type Response = GitLabApiResponse; + + fn raw_url(user: &str, repo: &str, commit: &str, file: &str) -> String { + format!( + "https://gitlab.com/{}/{}/raw/{}/{}", + user, repo, commit, file + ) + } + + fn api_url(path: &FilePath) -> String { + let repo_pattern = format!("{}/{}", path.user, path.repo).replace("/", "%2F"); + format!("https://gitlab.com/api/v4/projects/{}", repo_pattern) + // format!( + // "https://gitlab.com/api/v4/projects/{}/repository/branches/{}", + // path.repo, path.commit + // ) + } + + fn redirect_url(user: &str, repo: &str, commit: &str, file: &str) -> String { + format!("/gitlab/{}/{}/{}/{}", user, repo, commit, file) + } + + fn request_head( + mut response: ClientResponse, + data: web::Path, + client: web::Data, + ) -> Box> + where + S: 'static + Stream, + { + // "https://gitlab.com/api/v4/projects/{}/repository/branches/{}", + Box::new(match response.status() { + StatusCode::OK => Box::new( + response + .json::() + .map(move |resp| resp.id) + .from_err() + .and_then(move |repo_id| { + client + .get(format!( + "https://gitlab.com/api/v4/projects/{}/repository/branches/{}", + repo_id, data.commit + )) + .send() + .from_err() + .and_then(|mut respo| match respo.status() { + StatusCode::OK => Box::new( + respo + .json::() + .map(move |resp| { + HttpResponse::SeeOther() + .header( + LOCATION, + Self::redirect_url( + &data.user, + &data.repo, + resp.commit_ref(), + &data.file, + ) + .as_str(), + ) + .finish() + }) + .from_err(), + ) + as Box>, + code => Box::new(futures::future::ok( + HttpResponse::build(code).finish(), + )) + as Box>, + }) + .from_err() + }), + ) as Box>, + code => Box::new(futures::future::ok(HttpResponse::build(code).finish())) + as Box>, + }) + } +}