about summary refs log tree commit diff
path: root/splorm/src/handler
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2026-09-25 21:14:31 -0500
committergennyble <gen@nyble.dev>2026-09-25 21:14:31 -0500
commitfdbca76c341711e8516252b52e981ff0ebba133f (patch)
tree9a608806da2f0aa40016fbd79d758752fc06d5f7 /splorm/src/handler
parent761f67912f9a49736fb0fe6990192942d2522ecb (diff)
wikarden page U
Diffstat (limited to 'splorm/src/handler')
-rw-r--r--splorm/src/handler/quark/api.rs102
-rw-r--r--splorm/src/handler/quark/index.rs1
-rw-r--r--splorm/src/handler/quark/mod.rs74
3 files changed, 161 insertions, 16 deletions
diff --git a/splorm/src/handler/quark/api.rs b/splorm/src/handler/quark/api.rs
index 5d6f9ef..9513cbd 100644
--- a/splorm/src/handler/quark/api.rs
+++ b/splorm/src/handler/quark/api.rs
@@ -4,7 +4,9 @@ use axum::{Extension, body::Body, extract::Multipart, http::StatusCode, response
 use somethings::auth::Session;
 use wikarden::Wikarden;
 
-use crate::{RuntimeError, config::Settings, extractor::Wrap};
+use crate::{
+	RuntimeError, config::Settings, extractor::Wrap, fs, handler::quark::open_template_path,
+};
 
 /// POST /quark/api/create
 pub async fn create(
@@ -23,6 +25,7 @@ pub async fn create(
 		},
 		Operation::Create => {
 			let CreateForm { name, content }: CreateForm = data.try_into()?;
+			let redirect = api_redirect(&settings, &name, format!("../{name}"));
 
 			wikarden
 				.create(&name, content, false, format!("Create page '{name}'"))
@@ -30,14 +33,65 @@ pub async fn create(
 				.map_err(|_| RuntimeError::Unknown)?;
 
 			Response::builder()
-				.status(StatusCode::TEMPORARY_REDIRECT)
-				.header("Location", format!("../{name}"))
-				.body(Body::from(format!("redirecting to {name}")))
+				.status(StatusCode::OK)
+				.body(redirect.await?)
 				.map_err(|_| RuntimeError::Unknown)
 		}
+		_ => Err(RuntimeError::bad_request("usupported operation")),
 	}
 }
 
+/// POST /quark/api/edit
+pub async fn edit(
+	Extension(wikarden): Extension<Arc<Wikarden>>,
+	Extension(settings): Extension<Settings>,
+	Wrap(_session): Wrap<Session>,
+	mut multipart: Multipart,
+) -> Result<Response, RuntimeError> {
+	let data = MultipartFields::from_multipart(&mut multipart).await?;
+	let operation = data.operation()?;
+
+	match operation {
+		Operation::Preview => match data.content {
+			None => Err(RuntimeError::incomplete_form("content")),
+			Some(content) => super::markup::preview(&content, &settings).await,
+		},
+		Operation::Edit => {
+			let EditForm {
+				name,
+				content,
+				commit,
+			}: EditForm = data.try_into()?;
+			let redirect = api_redirect(&settings, &name, format!("../{name}"));
+
+			wikarden
+				.modify(&name, false, content, commit)
+				.await
+				.map_err(|_| RuntimeError::Unknown)?;
+
+			Response::builder()
+				.status(StatusCode::OK)
+				.body(redirect.await?)
+				.map_err(|_| RuntimeError::Unknown)
+		}
+		_ => Err(RuntimeError::bad_request("usupported operation")),
+	}
+}
+
+async fn api_redirect(
+	settings: &Settings,
+	page_name: &str,
+	page_location: String,
+) -> Result<Body, RuntimeError> {
+	let path = settings.quark_template_dir().join("api_redirect.html");
+	let mut doc = open_template_path(path, settings).await?;
+
+	doc.set("page-name", page_name);
+	doc.set("page-location", page_location);
+
+	Ok(Body::from(doc.compile()))
+}
+
 #[derive(Debug, PartialEq)]
 struct CreateForm {
 	name: String,
@@ -45,16 +99,26 @@ struct CreateForm {
 }
 
 #[derive(Debug, PartialEq)]
+struct EditForm {
+	name: String,
+	content: String,
+	commit: String,
+}
+
+#[derive(Debug, PartialEq)]
 enum Operation {
 	Create,
+	Edit,
 	Preview,
 }
 
 struct MultipartFields {
 	name: Option<String>,
 	content: Option<String>,
+	commit: Option<String>,
 
 	create: bool,
+	edit: bool,
 	preview: bool,
 }
 
@@ -62,8 +126,10 @@ impl MultipartFields {
 	pub async fn from_multipart(multipart: &mut Multipart) -> Result<Self, RuntimeError> {
 		let mut name = None;
 		let mut content = None;
+		let mut commit = None;
 
 		let mut create = false;
+		let mut edit = false;
 		let mut preview = false;
 
 		macro_rules! field_text {
@@ -81,9 +147,11 @@ impl MultipartFields {
 		{
 			match field.name() {
 				Some("create") => create = true,
+				Some("edit") => edit = true,
 				Some("preview") => preview = true,
 				Some("name") => name = field_text!(field "name"),
 				Some("content") => content = field_text!(field "content"),
+				Some("commit") => commit = field_text!(field "commit"),
 				_ => (),
 			}
 		}
@@ -91,15 +159,18 @@ impl MultipartFields {
 		Ok(MultipartFields {
 			name,
 			content,
+			commit,
 			create,
+			edit,
 			preview,
 		})
 	}
 
 	fn operation(&self) -> Result<Operation, RuntimeError> {
-		match (self.create, self.preview) {
-			(true, false) => Ok(Operation::Create),
-			(false, true) => Ok(Operation::Preview),
+		match (self.create, self.edit, self.preview) {
+			(true, false, false) => Ok(Operation::Create),
+			(false, true, false) => Ok(Operation::Edit),
+			(false, false, true) => Ok(Operation::Preview),
 
 			_ => Err(RuntimeError::bad_request("too many operations")),
 		}
@@ -112,9 +183,22 @@ impl TryInto<CreateForm> for MultipartFields {
 	fn try_into(self) -> Result<CreateForm, RuntimeError> {
 		match (self.name, self.content) {
 			(Some(name), Some(content)) => Ok(CreateForm { name, content }),
-			(None, Some(_)) => Err(RuntimeError::incomplete_form("name")),
-			(Some(_), None) => Err(RuntimeError::incomplete_form("content")),
 			_ => Err(RuntimeError::bad_request("create request missing fields")),
 		}
 	}
 }
+
+impl TryInto<EditForm> for MultipartFields {
+	type Error = RuntimeError;
+
+	fn try_into(self) -> Result<EditForm, RuntimeError> {
+		match (self.name, self.content, self.commit) {
+			(Some(name), Some(content), Some(commit)) => Ok(EditForm {
+				name,
+				content,
+				commit,
+			}),
+			_ => Err(RuntimeError::bad_request("edit request missing fields")),
+		}
+	}
+}
diff --git a/splorm/src/handler/quark/index.rs b/splorm/src/handler/quark/index.rs
index 983f76c..391a61c 100644
--- a/splorm/src/handler/quark/index.rs
+++ b/splorm/src/handler/quark/index.rs
@@ -39,6 +39,7 @@ pub async fn index(
 
 	let template_path = settings.quark_template_dir().join("page.html");
 	let mut template = super::open_template_path(template_path, &settings).await?;
+	template.set("title", "Quark");
 	template.set("main", "<h2>quark index</h2>");
 
 	Response::builder()
diff --git a/splorm/src/handler/quark/mod.rs b/splorm/src/handler/quark/mod.rs
index 1a9598c..7130d18 100644
--- a/splorm/src/handler/quark/mod.rs
+++ b/splorm/src/handler/quark/mod.rs
@@ -2,29 +2,33 @@ mod api;
 pub mod index;
 pub mod markup;
 
-use std::sync::Arc;
+use std::{str::FromStr, sync::Arc};
 
 use axum::{
 	Extension, Router,
 	body::Body,
 	extract::{Path, RawQuery},
+	http::{StatusCode, header},
 	response::Response,
 	routing::{get, post},
 };
 use bempline::{Document, Options, options::IncludeMethod};
 use camino::Utf8PathBuf;
+use somethings::auth::Session;
 use wikarden::Wikarden;
 
 use crate::{
-	RuntimeError,
+	Query, RuntimeError,
 	config::Settings,
-	fs::{self},
+	extractor::Wrap,
+	fs::{self, RequestPath, SafePath},
 	state::SplormState,
 };
 
 pub fn routes() -> Router<SplormState> {
 	Router::new()
 		.route("/api/create", post(api::create))
+		.route("/api/edit", post(api::edit))
 		.route("/", get(index::index))
 		.route("/{name}", get(page))
 }
@@ -33,12 +37,24 @@ async fn page(
 	Extension(wikarden): Extension<Arc<Wikarden>>,
 	Extension(settings): Extension<Settings>,
 	Path(name): Path<String>,
-	RawQuery(_rquery): RawQuery,
+	maybe_session: Option<Wrap<Session>>,
+	RawQuery(rquery): RawQuery,
 ) -> Result<Response, RuntimeError> {
-	//TODO: parse rquery for "edit"
+	if let Some(rq) = rquery {
+		if let Ok(q) = Query::from_str(&rq) {
+			if maybe_session.is_none() {
+				return Err(RuntimeError::Generic403);
+			}
+
+			if q.has("edit") {
+				return page_edit(name, &wikarden, settings).await;
+			}
+		}
+	}
 
 	let template_path = settings.quark_template_dir().join("page.html");
 	let mut template = open_template_path(template_path, &settings).await?;
+	template.set("title", &name);
 
 	match wikarden.source(name, false).await {
 		Err(e) => template.set("main", format!("<h2>{e:?}</h2>")),
@@ -48,8 +64,40 @@ async fn page(
 	}
 
 	Response::builder()
-		.status(200)
-		.header("Content-Type", "text/html")
+		.status(StatusCode::OK)
+		.header(header::CONTENT_TYPE, "text/html")
+		.body(Body::from(template.compile()))
+		.map_err(|_| RuntimeError::Unknown)
+}
+
+async fn page_edit(
+	page: String,
+	wik: &Wikarden,
+	settings: Settings,
+) -> Result<Response, RuntimeError> {
+	let template_path = settings.quark_template_dir().join("minimal.html");
+	let mut template = open_template_path(template_path, &settings).await?;
+
+	let rp = RequestPath::verify_safe(settings.root(), "quark/edit.html")?;
+	let edit = fs::read_file_to_string(rp.system_path()).await?;
+	let mut page_template = open_template(&edit, rp.system_path(), &settings).await?;
+
+	let src = match wik.source(&page, false).await {
+		//FIXME: Make a variant for unfound wiki pages
+		Err(_e) => return Err(RuntimeError::Generic404),
+		Ok(src) => src,
+	};
+
+	page_template.set("page-editing", page);
+	page_template.set("page-content", src.data);
+
+	template.set("mainClass", "main-create");
+	template.set("quarkForm", "true");
+	template.set("main", page_template.compile());
+
+	Response::builder()
+		.status(StatusCode::OK)
+		.header(header::CONTENT_TYPE, "text/html")
 		.body(Body::from(template.compile()))
 		.map_err(|_| RuntimeError::Unknown)
 }
@@ -66,3 +114,15 @@ pub async fn open_template_path<P: Into<Utf8PathBuf>>(
 	));
 	Document::from_str(str, options).map_err(|perr| RuntimeError::template_parse_error(perr, path))
 }
+
+pub async fn open_template<'p>(
+	content: &str,
+	path: SafePath<'p>,
+	settings: &Settings,
+) -> Result<Document, RuntimeError> {
+	let options = Options::new().include_path(IncludeMethod::Path(
+		settings.quark_template_dir().into_std_path_buf(),
+	));
+	Document::from_str(content, options)
+		.map_err(|perr| RuntimeError::template_parse_error(perr, path.to_path_buf()))
+}