More gui
This commit is contained in:
17 files changed
+1043
-31
No files matched your search
+5
-3
@@ -11,7 +11,7 @@ mod db;
|
||||
mod paste;
|
||||
mod routes;
|
||||
use crate::routes::{
|
||||
delete_note, get_note, get_redirect, home_page, list_notes, post_note, post_once,
|
||||
delete_note, get_note, get_redirect, gui_page, home_page, list_notes, post_note, post_once,
|
||||
post_redirect, put_note, put_redirect,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(home_page).post(post_note))
|
||||
.route("/gui", get(gui_page))
|
||||
.route("/once", post(post_once))
|
||||
.route("/once/{id}", post(post_once))
|
||||
.route("/to", post(post_redirect))
|
||||
@@ -46,8 +47,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
))
|
||||
.with_state(pool);
|
||||
|
||||
let listener = TcpListener::bind("0.0.0.0:3000").await?;
|
||||
println!("Listening on http://localhost:3000");
|
||||
let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_owned());
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{port}")).await?;
|
||||
println!("Listening on http://localhost:{port}");
|
||||
serve(listener, app).await?;
|
||||
Ok(())
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
#[derive(serde::Serialize, sqlx::FromRow)]
|
||||
pub struct Paste {
|
||||
pub id: String,
|
||||
#[sqlx(rename = "type")]
|
||||
@@ -15,7 +15,7 @@ pub struct Paste {
|
||||
pub owner: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type)]
|
||||
#[derive(serde::Serialize, sqlx::Type)]
|
||||
#[repr(i64)]
|
||||
pub enum PasteType {
|
||||
TextV1 = 0,
|
||||
|
||||
@@ -4,6 +4,7 @@ mod create;
|
||||
mod delete_note;
|
||||
mod get_note;
|
||||
mod get_redirect;
|
||||
mod gui;
|
||||
mod home;
|
||||
mod input;
|
||||
mod list_notes;
|
||||
@@ -18,6 +19,7 @@ mod update;
|
||||
pub use delete_note::delete_note;
|
||||
pub use get_note::get_note;
|
||||
pub use get_redirect::get_redirect;
|
||||
pub use gui::gui_page;
|
||||
pub use home::home_page;
|
||||
pub use list_notes::list_notes;
|
||||
pub use post_note::post_note;
|
||||
|
||||
+22
-5
@@ -1,4 +1,5 @@
|
||||
use axum::{Extension, extract::Path, http::StatusCode};
|
||||
use irosashi::Highlighter;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::{auth::User, paste::PasteType};
|
||||
@@ -9,21 +10,30 @@ pub async fn create(
|
||||
db: &SqlitePool,
|
||||
id: Option<Path<String>>,
|
||||
user: Option<Extension<User>>,
|
||||
input: PasteInput,
|
||||
mut input: PasteInput,
|
||||
paste_type: PasteType,
|
||||
) -> Result<String, StatusCode> {
|
||||
let mut id = id.map(|Path(id)| id).filter(|id| {
|
||||
!matches!(id.as_str(), "list" | "once" | "to" | "static") && !id.contains('/')
|
||||
!matches!(id.as_str(), "gui" | "list" | "once" | "to" | "static") && !id.contains('/')
|
||||
});
|
||||
|
||||
if matches!(paste_type, PasteType::TextV1) && input.language.is_none() {
|
||||
let highlighter = Highlighter::new().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
input.language = highlighter
|
||||
.detect_language_by_first_line(input.content.lines().next().unwrap_or_default());
|
||||
}
|
||||
|
||||
let owner = user.map(|Extension(user)| user.username);
|
||||
let expiration = input.expiration()?;
|
||||
|
||||
loop {
|
||||
let candidate = id.take().unwrap_or_else(generate_id);
|
||||
let created = sqlx::query_scalar(
|
||||
"INSERT INTO notes (id, type, content, language, expiration, max_views, created_at, owner)
|
||||
VALUES (COALESCE(?, lower(hex(randomblob(12)))), ?, ?, ?, ?, ?, unixepoch(), ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, unixepoch(), ?)
|
||||
ON CONFLICT(id) DO NOTHING RETURNING id",
|
||||
)
|
||||
.bind(id.as_deref())
|
||||
.bind(candidate)
|
||||
.bind(&paste_type)
|
||||
.bind(&input.content)
|
||||
.bind(&input.language)
|
||||
@@ -33,9 +43,16 @@ pub async fn create(
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.map_err(db_error)?;
|
||||
|
||||
if let Some(id) = created {
|
||||
return Ok(id);
|
||||
}
|
||||
id = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_id() -> String {
|
||||
const ALPHABET: &[u8] = b"23456789ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz";
|
||||
(0..8)
|
||||
.map(|_| ALPHABET[rand::random_range(0..ALPHABET.len())] as char)
|
||||
.collect()
|
||||
}
|
||||
+13
-5
@@ -2,7 +2,7 @@ use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
http::{HeaderMap, HeaderValue, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use irosashi::{CodeToHtmlOptions, Highlighter};
|
||||
@@ -15,6 +15,7 @@ use super::{db_error, return_html};
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "note.html")]
|
||||
pub struct NotePage {
|
||||
paste: Paste,
|
||||
code: String,
|
||||
}
|
||||
|
||||
@@ -28,7 +29,13 @@ pub async fn get_note(
|
||||
.map_err(db_error)?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
if return_html(&headers) {
|
||||
let mut metadata = HeaderMap::new();
|
||||
metadata.insert("x-paste-views", HeaderValue::from(paste.views));
|
||||
if let Some(max) = paste.max_views {
|
||||
metadata.insert("x-paste-max-views", HeaderValue::from(max));
|
||||
}
|
||||
|
||||
let response = if return_html(&headers) {
|
||||
let highlighter = Highlighter::new().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let code = highlighter
|
||||
.code_to_html(
|
||||
@@ -39,8 +46,9 @@ pub async fn get_note(
|
||||
),
|
||||
)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(NotePage { code }.into_response())
|
||||
NotePage { paste, code }.into_response()
|
||||
} else {
|
||||
Ok(paste.content.into_response())
|
||||
}
|
||||
paste.content.into_response()
|
||||
};
|
||||
Ok((metadata, response).into_response())
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use axum::http::StatusCode;
|
||||
use irosashi::Highlighter;
|
||||
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "gui.html")]
|
||||
pub struct GuiPage {
|
||||
languages: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn gui_page() -> Result<GuiPage, StatusCode> {
|
||||
let highlighter = Highlighter::new().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
Ok(GuiPage {
|
||||
languages: highlighter.languages(),
|
||||
})
|
||||
}
|
||||
+42
-2
@@ -1,10 +1,50 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use pulldown_cmark::{Event, Options, Parser, Tag, html};
|
||||
|
||||
static README_HTML: LazyLock<String> = LazyLock::new(|| {
|
||||
let events: Vec<_> = Parser::new_ext(
|
||||
include_str!("../../README.md"),
|
||||
Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TASKLISTS,
|
||||
)
|
||||
.collect();
|
||||
let anchored = events.iter().enumerate().map(|(index, event)| {
|
||||
if let Event::Start(Tag::Heading(level, None, _)) = event {
|
||||
let title: String = events[index + 1..]
|
||||
.iter()
|
||||
.take_while(|event| !matches!(event, Event::End(Tag::Heading(..))))
|
||||
.filter_map(|event| match event {
|
||||
Event::Text(text) | Event::Code(text) => Some(text.as_ref()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let slug: String = title
|
||||
.to_lowercase()
|
||||
.chars()
|
||||
.filter(|character| {
|
||||
character.is_alphanumeric() || *character == '-' || character.is_whitespace()
|
||||
})
|
||||
.collect();
|
||||
let slug = slug.split_whitespace().collect::<Vec<_>>().join("-");
|
||||
return Event::Html(format!("<{level} id=\"{slug}\">").into());
|
||||
}
|
||||
event.clone()
|
||||
});
|
||||
let mut rendered = String::new();
|
||||
html::push_html(&mut rendered, anchored);
|
||||
rendered
|
||||
});
|
||||
|
||||
#[derive(Template, WebTemplate)]
|
||||
#[template(path = "home.html")]
|
||||
pub struct HomePage;
|
||||
pub struct HomePage {
|
||||
readme: &'static str,
|
||||
}
|
||||
|
||||
pub async fn home_page() -> HomePage {
|
||||
HomePage
|
||||
HomePage {
|
||||
readme: &README_HTML,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use askama::Template;
|
||||
use askama_web::WebTemplate;
|
||||
use axum::{
|
||||
Extension,
|
||||
Extension, Json,
|
||||
extract::State,
|
||||
http::{HeaderMap, StatusCode},
|
||||
http::{HeaderMap, StatusCode, header::ACCEPT},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
@@ -38,7 +38,13 @@ pub async fn list_notes(
|
||||
.await
|
||||
.map_err(db_error)?;
|
||||
|
||||
if return_html(&headers) {
|
||||
if headers
|
||||
.get(ACCEPT)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|accept| accept.contains("application/json"))
|
||||
{
|
||||
Ok(Json(notes).into_response())
|
||||
} else if return_html(&headers) {
|
||||
Ok(ListPage { notes }.into_response())
|
||||
} else {
|
||||
Ok(notes
|
||||
|
||||
Reference in new issue
Block a user