diff --git a/Cargo.lock b/Cargo.lock index 8488b22..9c1707c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,30 +82,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attobin" -version = "0.2.1" -dependencies = [ - "axum", - "axum-extra", - "dotenv", - "glob", - "nanoneo", - "pathbufd", - "regex", - "serde", - "serde_json", - "serde_valid", - "tera", - "tetratto-core", - "tetratto-shared", - "tokio", - "toml 0.9.2", - "tower-http", - "tracing", - "tracing-subscriber", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -629,6 +605,30 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fluffle" +version = "0.2.1" +dependencies = [ + "axum", + "axum-extra", + "dotenv", + "glob", + "nanoneo", + "pathbufd", + "regex", + "serde", + "serde_json", + "serde_valid", + "tera", + "tetratto-core", + "tetratto-shared", + "tokio", + "toml 0.9.2", + "tower-http", + "tracing", + "tracing-subscriber", +] + [[package]] name = "fnv" version = "1.0.7" diff --git a/Cargo.toml b/Cargo.toml index 75e7458..23a5455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "attobin" +name = "fluffle" version = "0.2.1" edition = "2024" authors = ["trisuaso"] -repository = "https://trisua.com/t/tetratto" +repository = "https://trisua.com/t/fluffle" license = "AGPL-3.0-or-later" -homepage = "https://tetratto.com" +homepage = "https://fluffle.cc" [dependencies] tetratto-core = "12.0.2" diff --git a/README.md b/README.md index ac6b8cf..f21eea0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# 🦌 attobin +# 🐰 fluffle -Attobin is a simple Rentry (~December 2024) clone that uses a Tetratto app as a backend. +Fluffle is a familiar Markdown pastebin-y site :) Since Tetratto is used as a backend, you'll obviously need to create an app at . Once you've created the app, scroll down to "Secret key" and roll the key. Copy the key since you'll need it for later. @@ -34,10 +34,10 @@ You can build (without running) for release using the following command: cargo build -r ``` -Once you've built the binary, it'll be located at (from the root `attobin/` directory) `target/release/attobin`. +Once you've built the binary, it'll be located at (from the root `fluffle/` directory) `target/release/fluffle`. All templates are compiled with [nanoneo](https://trisua.com/t/nanoneo), so it's recommended that you familiarize yourself with that syntax. ## Attribution -Attobin is licensed under the AGPL-3.0 license. Tetratto is also licensed under the AGPL-3.0 license. Attobin is not affiliated with [Tetratto](https://tetratto.com) or [Rentry](https://rentry.co). +Fluffle is licensed under the AGPL-3.0 license. Tetratto is also licensed under the AGPL-3.0 license. diff --git a/app/docs/metadata.md b/app/docs/metadata.md index 1fd63fb..53132bf 100644 --- a/app/docs/metadata.md +++ b/app/docs/metadata.md @@ -8,4 +8,4 @@ All option names can either be in all lowercase, or all uppercase. While option Metadata options go in the "Metadata" tab in the entry editor page. Each option should be on a new line, and should be formatted as `NAME = value`. If you're familiar with TOML, you should be comfortable with metadata formatting. -You can view a list of all options and what they do [here](/public/reference/attobin/model/struct.EntryMetadata.html#fields). +You can view a list of all options and what they do [here](/public/reference/fluffle/model/struct.EntryMetadata.html#fields). diff --git a/app/public/app.js b/app/public/app.js index 5881a10..469f29b 100644 --- a/app/public/app.js +++ b/app/public/app.js @@ -4,8 +4,8 @@ function media_theme_pref() { if ( window.matchMedia("(prefers-color-scheme: dark)").matches && - (!window.localStorage.getItem("attobin:theme") || - window.localStorage.getItem("attobin:theme") === "Auto") + (!window.localStorage.getItem("fluffle:theme") || + window.localStorage.getItem("fluffle:theme") === "Auto") ) { document.documentElement.classList.add("dark"); @@ -13,16 +13,16 @@ function media_theme_pref() { document.getElementById("switch_dark").classList.remove("hidden"); } else if ( window.matchMedia("(prefers-color-scheme: light)").matches && - (!window.localStorage.getItem("attobin:theme") || - window.localStorage.getItem("attobin:theme") === "Auto") + (!window.localStorage.getItem("fluffle:theme") || + window.localStorage.getItem("fluffle:theme") === "Auto") ) { document.documentElement.classList.remove("dark"); document.getElementById("switch_light").classList.remove("hidden"); document.getElementById("switch_dark").classList.add("hidden"); - } else if (window.localStorage.getItem("attobin:theme")) { + } else if (window.localStorage.getItem("fluffle:theme")) { /* restore theme */ - const current = window.localStorage.getItem("attobin:theme"); + const current = window.localStorage.getItem("fluffle:theme"); document.documentElement.className = current.toLowerCase(); if (current === "Light") { @@ -48,7 +48,7 @@ globalThis.temporary_set_theme = (theme) => { }; globalThis.set_theme = (theme) => { - window.localStorage.setItem("attobin:theme", theme); + window.localStorage.setItem("fluffle:theme", theme); document.documentElement.className = theme; media_theme_pref(); }; diff --git a/app/templates_src/root.lisp b/app/templates_src/root.lisp index 7bd6036..98cadf5 100644 --- a/app/templates_src/root.lisp +++ b/app/templates_src/root.lisp @@ -44,7 +44,7 @@ (text "what")) (a ("class" "button") - ("href" "https://trisua.com/t/attobin") + ("href" "https://trisua.com/t/fluffle") (text "source")))) (a diff --git a/src/main.rs b/src/main.rs index 7d50353..e93e886 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,7 +121,7 @@ async fn main() { .await .unwrap(); - info!("🦌 attobin."); + info!("🐰 fluffle."); info!("listening on http://0.0.0.0:{}", port); axum::serve( listener, diff --git a/src/model.rs b/src/model.rs index 18793f5..b1c6594 100644 --- a/src/model.rs +++ b/src/model.rs @@ -13,6 +13,9 @@ pub struct Entry { pub content: String, #[serde(default)] pub metadata: String, + /// The IP address of the last editor of the entry. + #[serde(default)] + pub last_edit_from: String, } #[derive(Serialize, Deserialize, PartialEq, Eq)] diff --git a/src/routes.rs b/src/routes.rs index 3a8c7f1..fba3c6c 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,3 +1,5 @@ +use std::env::var; + use crate::{ State, model::{Entry, EntryMetadata}, @@ -5,6 +7,7 @@ use crate::{ use axum::{ Extension, Json, Router, extract::Path, + http::{HeaderMap, HeaderValue}, response::{Html, IntoResponse}, routing::{get, get_service, post}, }; @@ -48,18 +51,15 @@ pub fn routes() -> Router { fn default_context(data: &DataClient, build_code: &str) -> Context { let mut ctx = Context::new(); - ctx.insert( - "name", - &std::env::var("NAME").unwrap_or("Attobin".to_string()), - ); + ctx.insert("name", &var("NAME").unwrap_or("Fluffle".to_string())); ctx.insert( "theme_color", - &std::env::var("THEME_COLOR").unwrap_or("#fbc27f".to_string()), + &var("THEME_COLOR").unwrap_or("#a3b3ff".to_string()), ); ctx.insert("tetratto", &data.host); ctx.insert( "what_page_slug", - &std::env::var("WHAT_SLUG").unwrap_or("what".to_string()), + &var("WHAT_SLUG").unwrap_or("what".to_string()), ); ctx.insert("build_code", &build_code); ctx @@ -306,11 +306,27 @@ const CREATE_WAIT_TIME: usize = 5000; async fn create_request( jar: CookieJar, + headers: HeaderMap, Extension(data): Extension, Json(req): Json, ) -> std::result::Result>> { let (ref data, _, _) = *data.read().await; + // get real ip + let real_ip = headers + .get(var("REAL_IP_HEADER").unwrap_or("CF-Connecting-IP".to_string())) + .unwrap_or(&HeaderValue::from_static("")) + .to_str() + .unwrap_or("") + .to_string(); + + // check for ip ban + if !real_ip.is_empty() { + if data.check_ip(&real_ip.as_str()).await.is_ok() { + return Err(Json(Error::NotAllowed.into())); + } + } + // check wait time if let Some(cookie) = jar.get("__Secure-Claim-Next") { if unix_epoch_timestamp() @@ -394,6 +410,7 @@ async fn create_request( edited: created, content: req.content, metadata: req.metadata, + last_edit_from: real_ip, }) .unwrap(), ) @@ -441,12 +458,28 @@ struct EditEntry { } async fn edit_request( + headers: HeaderMap, Extension(data): Extension, Path(slug): Path, Json(req): Json, ) -> impl IntoResponse { let (ref data, _, _) = *data.read().await; + // get real ip + let real_ip = headers + .get(var("REAL_IP_HEADER").unwrap_or("CF-Connecting-IP".to_string())) + .unwrap_or(&HeaderValue::from_static("")) + .to_str() + .unwrap_or("") + .to_string(); + + // check for ip ban + if !real_ip.is_empty() { + if data.check_ip(&real_ip.as_str()).await.is_ok() { + return Json(Error::NotAllowed.into()); + } + } + // check content length if req.content.len() < 2 { return Json(Error::DataTooShort("content".to_string()).into()); @@ -586,6 +619,7 @@ async fn edit_request( // update entry.content = req.content; entry.metadata = req.metadata; + entry.last_edit_from = real_ip; entry.edited = unix_epoch_timestamp(); if let Err(e) = data