add sql warcrimes

This commit is contained in:
Hamcha 2023-06-29 00:59:26 +02:00
parent 8eb70cdc40
commit 0811e89e65
Signed by: hamcha
GPG key ID: 1669C533B8CF6D89
7 changed files with 182 additions and 12 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
DATABASE_URL=postgres://artificiale:changeme@localhost/artificiale

29
Cargo.lock generated
View file

@ -197,7 +197,11 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-traits", "num-traits",
"serde",
"time",
"wasm-bindgen",
"winapi", "winapi",
] ]
@ -408,7 +412,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -651,8 +655,10 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
"chrono",
"figment", "figment",
"serde", "serde",
"serde_json",
"sqlx", "sqlx",
"tokio", "tokio",
"tracing", "tracing",
@ -700,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys", "windows-sys",
] ]
@ -1241,6 +1247,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_json",
"sha2", "sha2",
"sqlx-core", "sqlx-core",
"sqlx-rt", "sqlx-rt",
@ -1333,6 +1340,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -1600,6 +1618,7 @@ checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"rand", "rand",
"serde",
] ]
[[package]] [[package]]
@ -1623,6 +1642,12 @@ dependencies = [
"try-lock", "try-lock",
] ]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View file

@ -8,8 +8,10 @@ axum = "0.6"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
tokio = { version = "1.28", features = ["full"] } tokio = { version = "1.28", features = ["full"] }
sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "uuid", "chrono", "macros", "migrate" ] } sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "uuid", "chrono", "macros", "migrate", "json" ] }
uuid = { version = "1.3", features = ["v4", "fast-rng"] } uuid = { version = "1.3", features = ["v4", "fast-rng", "serde"] }
serde = { version = "1" } serde = { version = "1" }
serde_json = { version = "1", features = ["raw_value"] }
figment = { version = "0.10", features = ["toml", "env"] } figment = { version = "0.10", features = ["toml", "env"] }
chrono = { version = "0.4", features = ["serde"] }
anyhow = "1.0" anyhow = "1.0"

View file

@ -0,0 +1 @@
DROP TABLE pages;

View file

@ -0,0 +1,12 @@
CREATE TABLE pages (
id UUID PRIMARY KEY,
author UUID NOT NULL,
slug VARCHAR NOT NULL,
title VARCHAR NOT NULL,
description TEXT,
tags VARCHAR[] NOT NULL,
blocks JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
modified_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ
);

78
src/content.rs Normal file
View file

@ -0,0 +1,78 @@
use chrono::{serde::*, DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{types::Json, FromRow};
use uuid::Uuid;
#[derive(Deserialize, Serialize, FromRow)]
pub struct Page {
/// Page ID
pub id: Uuid,
/// Author ID
pub author: Uuid,
/// URL-friendly short code for the page
pub slug: String,
/// Page title
pub title: String,
/// Page description (for SEO/content)
pub description: Option<String>,
/// Page tags (for internal search)
pub tags: Vec<String>,
/// Page blocks (content)
pub blocks: Json<Vec<PageBlock>>,
/// Times
#[serde(with = "ts_seconds")]
pub created_at: DateTime<Utc>,
#[serde(with = "ts_seconds_option")]
pub modified_at: Option<DateTime<Utc>>,
#[serde(with = "ts_seconds_option")]
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Deserialize, Serialize)]
pub enum PageBlock {
Markup(MarkupBlock),
Gallery(GalleryBlock),
}
/// 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<ImageElement>,
}
/// Picture inside a gallery
#[derive(Deserialize, Serialize)]
pub struct ImageElement {
/// URL of the picture
pub url: String,
/// Image width in pixels
pub width: i32,
/// Image height in pixels
pub height: i32,
/// List of thumbnails, if available
pub thumbnails: Vec<ImageElement>,
/// Optional caption to put near the image
pub caption: Option<String>,
}

View file

@ -1,12 +1,22 @@
use anyhow::{Ok, Result}; mod content;
use axum::{routing::get, Router, Server};
use anyhow::{anyhow, Result};
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
Router, Server,
};
use figment::{ use figment::{
providers::{Env, Format, Serialized, Toml}, providers::{Env, Format, Serialized, Toml},
Figment, Figment,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::postgres::PgPoolOptions; use sqlx::{postgres::PgPoolOptions, query_as, Pool, Postgres};
use std::net::SocketAddr; use std::{net::SocketAddr, sync::Arc};
use crate::content::{Page, PageBlock};
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct Config { struct Config {
@ -23,8 +33,47 @@ impl Default for Config {
} }
} }
async fn root() -> &'static str { struct GenericError(anyhow::Error);
"Hello, World!"
impl IntoResponse for GenericError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
impl From<sqlx::Error> for GenericError {
fn from(value: sqlx::Error) -> Self {
GenericError(anyhow!("Database error: {}", value))
}
}
struct AppState {
database: Pool<Postgres>,
}
async fn root(State(state): State<Arc<AppState>>) -> Result<String, GenericError> {
let page = query_as!(
Page,
r#"select
id,
author,
title,
description,
tags,
slug,
created_at,
modified_at,
deleted_at,
blocks as "blocks!: sqlx::types::Json<Vec<PageBlock>>"
from pages limit 1"#
)
.fetch_one(&state.database)
.await?;
Ok(page.title)
} }
#[tokio::main] #[tokio::main]
@ -36,8 +85,6 @@ async fn main() -> Result<()> {
.merge(Env::prefixed("MABEL_")) .merge(Env::prefixed("MABEL_"))
.extract()?; .extract()?;
let app = Router::new().route("/", get(root));
let pool = PgPoolOptions::new() let pool = PgPoolOptions::new()
.max_connections(5) .max_connections(5)
.connect(config.database_url.as_str()) .connect(config.database_url.as_str())
@ -45,6 +92,10 @@ async fn main() -> Result<()> {
sqlx::migrate!().run(&pool).await?; sqlx::migrate!().run(&pool).await?;
let shared_state = Arc::new(AppState { database: pool });
let app = Router::new().route("/", get(root)).with_state(shared_state);
let addr: SocketAddr = config.bind.parse()?; let addr: SocketAddr = config.bind.parse()?;
tracing::debug!("listening on {}", addr); tracing::debug!("listening on {}", addr);
Server::bind(&addr) Server::bind(&addr)