Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bojangalic/merge stelae and git command #64

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
74 changes: 73 additions & 1 deletion src/server/api/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@ use actix_service::ServiceFactory;
use actix_web::{
body::MessageBody,
dev::{ServiceRequest, ServiceResponse},
guard, web, App, Error, Scope,
guard, route, web, App, Error, HttpResponse, Responder, Scope,
};
use serde::Deserialize;

use super::state::App as AppState;
use super::{serve::serve, state::Global, versions::versions};
use crate::utils::git::{Repo, GIT_REQUEST_NOT_FOUND};
use crate::utils::http::get_contenttype;
use crate::utils::paths::clean_path;
use git2::{self, ErrorCode};

use super::super::errors::HTTPError;

/// Name of the header to guard current documents
static HEADER_NAME: OnceLock<String> = OnceLock::new();
Expand Down Expand Up @@ -72,6 +80,10 @@ pub fn register_app<
)
.app_data(web::Data::new(state.clone()));

app = app
.service(web::scope("/_git").service(get_blob))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.service(web::scope("/_git").service(get_blob))
.service(web::scope("/_stelae").service(get_blob))

.app_data(web::Data::new(state.clone()));

app = register_dynamic_routes(app, state)?;
Ok(app)
}
Expand Down Expand Up @@ -315,3 +327,63 @@ fn register_dependent_routes(
}
Ok(())
}

/// Structure for
#[derive(Debug, Deserialize)]
struct Info {
/// commit of the repo
commitish: String,
/// path of the file
remainder: Option<String>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: api/stelae/requests/mod.rs


/// Return the content in the stelae archive in the `{namespace}/{name}`
/// repo at the `commitish` commit at the `remainder` path.
/// Return 404 if any are not found or there are any errors.
#[route("/{namespace}/{name}", method = "GET", method = "HEAD")]
#[tracing::instrument(name = "Retrieving a Git blob", skip(path, data, info))]
#[expect(
clippy::future_not_send,
reason = "We don't worry about git2-rs not implementing `Send` trait"
)]
async fn get_blob(
path: web::Path<(String, String)>,
info: web::Query<Info>,
data: web::Data<AppState>,
) -> impl Responder {
let (namespace, name) = path.into_inner();
let info_struct: Info = info.into_inner();
let commitish = info_struct.commitish;
let remainder = info_struct.remainder.unwrap_or_default();
let archive_path = &data.archive_path;
let blob = Repo::find_blob(archive_path, &namespace, &name, &remainder, &commitish);
let blob_path = clean_path(&remainder);
let contenttype = get_contenttype(&blob_path);
match blob {
Ok(content) => HttpResponse::Ok().insert_header(contenttype).body(content),
Err(error) => blob_error_response(&error, &namespace, &name),
}
}

/// A centralised place to match potentially unsafe internal errors to safe user-facing error responses
#[expect(clippy::wildcard_enum_match_arm, reason = "Allows _ for enum matching")]
#[tracing::instrument(name = "Error with Git blob request", skip(error, namespace, name))]
fn blob_error_response(error: &anyhow::Error, namespace: &str, name: &str) -> HttpResponse {
tracing::error!("{error}",);
if let Some(git_error) = error.downcast_ref::<git2::Error>() {
return match git_error.code() {
// TODO: check this is the right error
ErrorCode::NotFound => {
HttpResponse::NotFound().body(format!("repo {namespace}/{name} does not exist"))
}
_ => HttpResponse::InternalServerError().body("Unexpected Git error"),
};
}
match error {
// TODO: Obviously it's better to use custom `Error` types
_ if error.to_string() == GIT_REQUEST_NOT_FOUND => {
HttpResponse::NotFound().body(HTTPError::NotFound.to_string())
}
_ => HttpResponse::InternalServerError().body(HTTPError::InternalServerError.to_string()),
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: move to api/stelae/mod.rs

8 changes: 8 additions & 0 deletions src/server/api/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub trait Global {
fn archive(&self) -> &Archive;
/// Database connection
fn db(&self) -> &db::DatabaseConnection;
/// path to the Stelae archive
fn archive_path(&self) -> &PathBuf;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: we don't need this, I think. Can get path from Archive struct

}

/// Application state
Expand All @@ -22,6 +24,8 @@ pub struct App {
pub archive: Archive,
/// Database connection
pub db: db::DatabaseConnection,
/// path to the Stelae archive
pub archive_path: PathBuf,
}

impl Global for App {
Expand All @@ -32,6 +36,10 @@ impl Global for App {
fn db(&self) -> &db::DatabaseConnection {
&self.db
}

fn archive_path(&self) -> &PathBuf {
&self.archive_path
}
}

/// Repository to serve
Expand Down
12 changes: 10 additions & 2 deletions src/server/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ pub async fn serve_archive(
}
};

let archive = match Archive::parse(archive_path, &PathBuf::from(raw_archive_path), individual) {
let archive = match Archive::parse(
archive_path.clone(),
&PathBuf::from(raw_archive_path),
individual,
) {
Ok(archive) => archive,
Err(err) => {
tracing::error!("Unable to parse archive at '{raw_archive_path}'.");
Expand All @@ -53,7 +57,11 @@ pub async fn serve_archive(
}
};

let state = AppState { archive, db };
let state = AppState {
archive,
db,
archive_path,
};

HttpServer::new(move || {
init(&state).unwrap_or_else(|err| {
Expand Down
112 changes: 0 additions & 112 deletions src/server/git.rs

This file was deleted.

1 change: 0 additions & 1 deletion src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@
pub mod api;
pub mod app;
pub mod errors;
pub mod git;
pub mod tracing;
8 changes: 0 additions & 8 deletions src/utils/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use crate::history::changes;
use crate::server::app::serve_archive;
use crate::server::errors::CliError;
use crate::server::git::serve_git;
use crate::utils::archive::find_archive_path;
use clap::Parser;
use std::env;
Expand Down Expand Up @@ -41,12 +40,6 @@ struct Cli {
/// Subcommands for the Stelae CLI
#[derive(Clone, clap::Subcommand)]
enum Subcommands {
/// Serve git repositories in the Stelae archive
Git {
/// Port on which to serve the archive.
#[arg(short, long, default_value_t = 8080)]
port: u16,
},
/// Serve documents in a Stelae archive.
Serve {
/// Port on which to serve the archive.
Expand Down Expand Up @@ -115,7 +108,6 @@ fn init_tracing(archive_path: &Path) {
/// This function returns the generic `CliError`, based on which we exit with a known exit code.
fn execute_command(cli: &Cli, archive_path: PathBuf) -> Result<(), CliError> {
match cli.subcommands {
Subcommands::Git { port } => serve_git(&cli.archive_path, archive_path, port),
Subcommands::Serve { port, individual } => {
serve_archive(&cli.archive_path, archive_path, port, individual)
}
Expand Down
3 changes: 3 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ impl Global for TestAppState {
fn db(&self) -> &db::DatabaseConnection {
unimplemented!()
}
fn archive_path(&self) -> &PathBuf {
unimplemented!()
}
}

pub async fn initialize_app(
Expand Down
Loading