add sql warcrimes
This commit is contained in:
parent
8eb70cdc40
commit
0811e89e65
7 changed files with 182 additions and 12 deletions
1
.env
Normal file
1
.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DATABASE_URL=postgres://artificiale:changeme@localhost/artificiale
|
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"
|
1
migrations/20230628223219_create-page.down.sql
Normal file
1
migrations/20230628223219_create-page.down.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE pages;
|
12
migrations/20230628223219_create-page.up.sql
Normal file
12
migrations/20230628223219_create-page.up.sql
Normal 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
78
src/content.rs
Normal 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>,
|
||||||
|
}
|
67
src/main.rs
67
src/main.rs
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue