diff --git a/Cargo.lock b/Cargo.lock index 3470b3d..3637b7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,12 +358,42 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "figment" version = "0.10.10" @@ -371,8 +401,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" dependencies = [ "atomic", + "parking_lot 0.12.1", "pear", "serde", + "tempfile", "toml", "uncased", "version_check", @@ -509,6 +541,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + [[package]] name = "hex" version = "0.4.3" @@ -648,6 +686,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -684,6 +733,12 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lock_api" version = "0.4.10" @@ -800,7 +855,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1053,6 +1108,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.37.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustls" version = "0.20.8" @@ -1378,6 +1447,20 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index 327383b..fc9311c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "uuid uuid = { version = "1.3", features = ["v4", "fast-rng", "serde"] } serde = { version = "1" } serde_json = { version = "1", features = ["raw_value"] } -figment = { version = "0.10", features = ["toml", "env"] } +figment = { version = "0.10", features = ["toml", "env", "test"] } chrono = { version = "0.4", features = ["serde", "clock"] } anyhow = "1.0" argon2 = { version = "0.5", features = ["std", "alloc"] } diff --git a/src/content.rs b/src/content.rs index c80cd36..940417f 100644 --- a/src/content.rs +++ b/src/content.rs @@ -65,26 +65,19 @@ pub struct Page { } #[derive(Deserialize, Serialize)] +#[serde(tag = "kind")] pub enum PageBlock { - Markup(MarkupBlock), - Gallery(GalleryBlock), -} + MarkupV1 { + /// Markup format (markdown, html, plain) + format: String, -/// A block of content in written form (probably Markdown) -#[derive(Deserialize, Serialize)] -pub struct MarkupBlock { - /// Markup format (markdown, html, plain) - pub format: String, - - /// Markup content (before rendering) - pub content: String, -} - -/// A block containing one or more images -#[derive(Deserialize, Serialize)] -pub struct GalleryBlock { - /// Images in the gallery - pub images: Vec, + /// Markup content (before rendering) + content: String, + }, + GalleryV1 { + /// Images in the gallery + images: Vec, + }, } /// Picture inside a gallery diff --git a/src/main.rs b/src/main.rs index 800f035..b9035b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ mod state; use crate::{auth::http::refresh_sessions, state::Config}; use anyhow::Result; -use axum::{middleware, Router, Server}; +use axum::{middleware, Server}; use figment::{ providers::{Env, Format, Serialized, Toml}, Figment, @@ -33,12 +33,12 @@ async fn main() -> Result<()> { sqlx::migrate!().run(&database).await?; + // Sanity check config + config.sanity_check(); + let shared_state = Arc::new(AppState { database, config }); - let app = Router::new() - .nest("/auth", routes::auth::router()) - .nest("/admin", routes::admin::router()) - .nest("/posts", routes::posts::router()) + let app = routes::create_router() .route_layer(middleware::from_fn_with_state( shared_state.clone(), refresh_sessions, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 90fd44d..81842dd 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,17 @@ -pub mod admin; -pub mod auth; -pub mod posts; +use axum::Router; +use std::sync::Arc; + +use crate::state::AppState; + +mod admin; +mod auth; +mod posts; +mod sites; + +pub fn create_router() -> Router> { + Router::new() + .nest("/auth", auth::router()) + .nest("/admin", admin::router()) + .nest("/posts", posts::router()) + .nest("/sites", sites::router()) +} diff --git a/src/routes/posts.rs b/src/routes/posts.rs index b05b976..f153899 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,7 +1,8 @@ use axum::{ extract::{Path, State}, + response::IntoResponse, routing::get, - Router, + Json, Router, }; use std::sync::Arc; @@ -10,14 +11,14 @@ use crate::{content::Page, error::AppError, state::AppState}; async fn get_page( State(state): State>, Path((site, slug)): Path<(String, String)>, -) -> Result> { +) -> Result> { let page_query = sqlx::query_as( "SELECT p.* FROM pages p JOIN sites s ON p.site = s.id WHERE p.slug = $1 AND s.name = $2", ) .bind(slug) .bind(site); let page: Page = page_query.fetch_one(&state.database).await?; - Ok(page.title) + Ok(Json(page)) } pub fn router() -> Router> { diff --git a/src/routes/sites.rs b/src/routes/sites.rs new file mode 100644 index 0000000..caae226 --- /dev/null +++ b/src/routes/sites.rs @@ -0,0 +1,41 @@ +use axum::{ + extract::{Path, State}, + response::IntoResponse, + routing::{get, post}, + Json, Router, +}; +use serde::Deserialize; +use serde_json::json; +use std::sync::Arc; + +use crate::{content::Site, error::AppError, state::AppState}; + +async fn get_site( + State(state): State>, + Path(name): Path, +) -> Result> { + let site_query = sqlx::query_as("SELECT * FROM sites WHERE name = $1").bind(name); + let site: Site = site_query.fetch_one(&state.database).await?; + Ok(Json(site)) +} + +#[derive(Deserialize)] +struct CreateSiteRequest { + name: String, +} + +async fn create_site( + State(state): State>, + Json(payload): Json, +) -> Result> { + sqlx::query!("INSERT INTO sites (name) VALUES ($1)", payload.name) + .execute(&state.database) + .await?; + Ok(Json(json!({"ok": true, "url": "test"}))) +} + +pub fn router() -> Router> { + Router::new() + .route("/", post(create_site)) + .route("/:name", get(get_site)) +} diff --git a/src/state.rs b/src/state.rs index b1cae8a..a1b690b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -24,6 +24,18 @@ impl Config { .map(|x| x.domain().unwrap_or("localhost").to_owned()) .unwrap_or("localhost".to_owned()) } + + pub fn sanity_check(&self) { + // check if base url is valid + if let Err(e) = Url::parse(&self.base_url) { + tracing::warn!("base URL is not valid: {}, falling back to localhost", e); + } + + // check that session duration is sensible + if self.session_duration < 100 { + tracing::warn!("session duration is too low, this might cause some headaches"); + } + } } impl Default for Config { @@ -32,7 +44,7 @@ impl Default for Config { bind: "127.0.0.1:3000".into(), database_url: "postgres://artificiale:changeme@localhost/artificiale".into(), session_duration: 3600, // 60min - prune_interval: 10, // 60min + prune_interval: 3600, // 60min base_url: "http://localhost".into(), } }