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 = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -408,7 +412,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -651,8 +655,10 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"chrono",
|
||||
"figment",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -700,7 +706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
|
@ -1241,6 +1247,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
|
@ -1333,6 +1340,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
|
@ -1600,6 +1618,7 @@ checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81"
|
|||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1623,6 +1642,12 @@ dependencies = [
|
|||
"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]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -8,8 +8,10 @@ axum = "0.6"
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tokio = { version = "1.28", features = ["full"] }
|
||||
sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "uuid", "chrono", "macros", "migrate" ] }
|
||||
uuid = { version = "1.3", features = ["v4", "fast-rng"] }
|
||||
sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "uuid", "chrono", "macros", "migrate", "json" ] }
|
||||
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"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
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};
|
||||
use axum::{routing::get, Router, Server};
|
||||
mod content;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
Router, Server,
|
||||
};
|
||||
use figment::{
|
||||
providers::{Env, Format, Serialized, Toml},
|
||||
Figment,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use std::net::SocketAddr;
|
||||
use sqlx::{postgres::PgPoolOptions, query_as, Pool, Postgres};
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
|
||||
use crate::content::{Page, PageBlock};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Config {
|
||||
|
@ -23,8 +33,47 @@ impl Default for Config {
|
|||
}
|
||||
}
|
||||
|
||||
async fn root() -> &'static str {
|
||||
"Hello, World!"
|
||||
struct GenericError(anyhow::Error);
|
||||
|
||||
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]
|
||||
|
@ -36,8 +85,6 @@ async fn main() -> Result<()> {
|
|||
.merge(Env::prefixed("MABEL_"))
|
||||
.extract()?;
|
||||
|
||||
let app = Router::new().route("/", get(root));
|
||||
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(config.database_url.as_str())
|
||||
|
@ -45,6 +92,10 @@ async fn main() -> Result<()> {
|
|||
|
||||
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()?;
|
||||
tracing::debug!("listening on {}", addr);
|
||||
Server::bind(&addr)
|
||||
|
|
Loading…
Reference in a new issue