Skip to content

Commit

Permalink
feat: backend delete files + git add refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
zleyyij committed Sep 5, 2024
1 parent c61ec67 commit 35954c8
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 9 deletions.
86 changes: 78 additions & 8 deletions backend/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use color_eyre::eyre::{bail, ContextCompat};
use color_eyre::{eyre::Context, Result};
use git2::{AnnotatedCommit, FetchOptions, Oid, Repository, Signature};
use git2::{AnnotatedCommit, FetchOptions, Oid, Repository, Signature, Status};
use serde::{Deserialize, Serialize};
use std::fs;
use std::io::{Read, Write};
Expand All @@ -16,8 +16,10 @@ use tracing::{debug, info, warn};

#[derive(Clone)]
pub struct Interface {
#[allow(dead_code)] // Will be used later
repo: Arc<Mutex<Repository>>,
/// The path to the documents folder, relative to the server executable.
///
/// EG: `./repo/docs`
doc_path: PathBuf,
}

Expand All @@ -36,12 +38,12 @@ impl Interface {
/// This function will return an error if any of the git initialization steps fail, or if
/// the required environment variables are not set.
pub fn new() -> Result<Self> {
let mut doc_path = PathBuf::from("./repo");
let mut doc_path = PathBuf::from("repo");
doc_path.push(env::var("DOC_PATH").unwrap_or_else(|_| {
warn!("The `DOC_PATH` environment variable was not set, defaulting to `docs/`");
"docs".to_string()
}));
let repo = Self::load_repository("./repo")?;
let repo = Self::load_repository("repo")?;
Ok(Self {
repo: Arc::new(Mutex::new(repo)),
doc_path,
Expand Down Expand Up @@ -148,6 +150,7 @@ impl Interface {
)
})?;
let msg = format!("[Hyde]: {message}");
// Relative to the root of the repo, not the current dir, so typically `./docs` instead of `./repo/docs`
let mut relative_path = PathBuf::from(
env::var("DOC_PATH").wrap_err("The `DOC_PATH` environment variable was not set")?,
);
Expand All @@ -165,6 +168,47 @@ impl Interface {
Ok(())
}

/// Delete the document at the specified `path`.
/// `message` will be included in the commit message, and `token` is a valid github auth token.
///
/// # Panics
/// This function will panic if it's called when the repo mutex is already held by the current thread
///
/// # Errors
/// This function will return an error if filesystem operations fail, or if any of the git operations fail
// This lint gets upset that `repo` isn't dropped early because it's a performance heavy drop, but when applied,
// it creates errors that note the destructor for other values failing because of it (tree)
pub fn delete_doc<P: AsRef<Path> + Copy>(
&self,
path: P,
message: &str,
token: &str,
) -> Result<()> {
let repo = self.repo.lock().unwrap();
let mut path_to_doc: PathBuf = PathBuf::new();
path_to_doc.push(&self.doc_path);
path_to_doc.push(path);
let msg = format!("[Hyde]: {message}");
// Relative to the root of the repo, not the current dir, so typically `./docs` instead of `./repo/docs`
let mut relative_path = PathBuf::from(
env::var("DOC_PATH").wrap_err("The `DOC_PATH` environment variable was not set")?,
);
// Standard practice is to stage commits by adding them to an index.
relative_path.push(path);
fs::remove_file(&path_to_doc).wrap_err_with(|| format!("Failed to remove document the document at {path_to_doc:?}"))?;
Self::git_add(&repo, ".")?;
let commit_id = Self::git_commit(&repo, msg, None)?;
debug!("New commit made with ID: {:?}", commit_id);
Self::git_push(&repo, token)?;
drop(repo);
info!(
"Document {:?} removed and changes synced to Github with message: {message:?}",
path.as_ref()
);
debug!("Commit cleanup completed");
Ok(())
}

/// If the repository at the provided path exists, open it and fetch the latest changes from the `master` branch.
/// If not, clone into the provided path.
#[tracing::instrument]
Expand Down Expand Up @@ -218,13 +262,41 @@ impl Interface {
}

/// A code level re-implementation of `git add`.
fn git_add<P: AsRef<Path>>(repo: &Repository, path: P) -> Result<()> {
#[tracing::instrument(skip(repo))]
fn git_add<P: AsRef<Path> + std::fmt::Debug>(repo: &Repository, path: P) -> Result<()> {
let mut index = repo.index()?;
index.add_path(path.as_ref())?;
// index.add_path(path.as_ref())?;
let callback = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
let status = repo.status_file(path).unwrap();
let actions = vec![
(Status::WT_DELETED, "deleted"),
(Status::WT_MODIFIED, "modified"),
(Status::WT_NEW, "added"),
(Status::WT_RENAMED, "renamed")
];

for (action, msg) in actions {
if status.contains(action) {
info!("Index updated, {path:?} will be {msg} in the next commit");
}
}
0
};

index.update_all([path.as_ref()], Some(callback))?;
index.write()?;
Ok(())
}

// /// A code level re-implementation of `git rm`.
// fn git_rm<P: AsRef<Path>>(repo: &Repository, path: P) -> Result<()> {
// let mut index = repo.index()?;
// // index.add_path(path.as_ref())?;
// index.remove(path.as_ref(), 1)?;
// index.write()?;
// Ok(())
// }

/// A code level re-implementation of `git commit`.
///
/// Writes the current index as a commit, updating HEAD. This means it will only commit changes
Expand Down Expand Up @@ -295,8 +367,6 @@ impl Interface {
Some(&mut fetch_options),
None,
)?;
// Stuff with C bindings will sometimes require manual dropping if
// there's references and stuff
drop(remote);

let fetch_head = repo.find_reference("FETCH_HEAD")?;
Expand Down
20 changes: 20 additions & 0 deletions backend/src/handlers_prelude/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use tracing::{error, warn};

use crate::{perms::Permission, require_perms, AppState};

use super::eyre_to_axum_err;

#[derive(Debug, Deserialize, Serialize)]
pub struct GetDocQuery {
pub path: String,
Expand Down Expand Up @@ -96,3 +98,21 @@ pub async fn put_doc_handler(
}
}
}

pub async fn delete_doc_handler(
State(state): State<AppState>,
headers: HeaderMap,
Query(query): Query<GetDocQuery>,
) -> Result<StatusCode, (StatusCode, String)> {
let author = require_perms(
axum::extract::State(&state),
headers,
&[Permission::ManageContent],
)
.await?;

let gh_token = state.gh_credentials.get(&state.reqwest_client).await.unwrap();
state.git.delete_doc(&query.path, &format!("{} deleted {}", author.username, query.path), &gh_token).map_err(eyre_to_axum_err)?;

Ok(StatusCode::NO_CONTENT)
}
1 change: 1 addition & 0 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ async fn start_server(state: AppState, cli_args: Args) -> Result<()> {
.route("/api/hooks/github", post(github_hook_handler))
.route("/api/doc", get(get_doc_handler))
.route("/api/doc", put(put_doc_handler))
.route("/api/doc", delete(delete_doc_handler))
.route("/api/tree", get(get_tree_handler))
.route("/api/oauth", get(get_oauth2_handler))
.route("/api/oauth/url", get(get_oauth2_url))
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/lib/components/FileNavigation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import { cache } from '$lib/cache';
import { get } from 'svelte/store';
import ConfirmationDialogue from './ConfirmationDialogue.svelte';
import { apiAddress } from '$lib/net';
import { addToast, ToastType } from '$lib/toast';
interface INode {
name: string;
children: INode[];
Expand Down Expand Up @@ -68,7 +70,22 @@
}
// TODO: requisite backend work, eg create DELETE
// handler for documents.
const r = await fetch(`${apiAddress}/api/doc?path=${path}`, {method: "DELETE", credentials: "include"});
if (r.ok) {
addToast({
message: `The file "${path}" was deleted successfully."`,
type: ToastType.Info,
dismissible: true,
timeout: 1500
});
} else {
addToast({
message: `Deletion failed, please report to the developer`,
type: ToastType.Error,
dismissible: true,
timeout: 1500
});
}
// While a re-render would happen when the directory
// is closed and re-opened, I nuke the current element here
// because I don't know how else to make it happen immediately
Expand Down

0 comments on commit 35954c8

Please sign in to comment.