[#2] Refactored and added SQLX schema files.

This commit is contained in:
2025-08-29 19:09:05 -04:00
parent 0a16667b76
commit 7b5c69cc50
17 changed files with 242 additions and 54 deletions

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR IGNORE INTO tale_tags (tale_slug, tag_id)\n VALUES (?1, ?2)",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "0f58e58ff3bcda95267629c8dbde46f85218872afb759b07d5aa217309555b99"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR REPLACE INTO tavern (key, value) VALUES ('title', ?1)",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "2b2ddeb7ea1690d809f237afc8d21aa67599591bcbc9671d31c6ed90aded502d"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR REPLACE INTO adventurers (\n handle, name, profile, image, blurb\n ) VALUES (?1, ?2, ?3, ?4, ?5)",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "40a4dcf62e6741b1e2b669469704325d06de1327c453945128ce2e0a1edf510d"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "CREATE TABLE IF NOT EXISTS tags (\n id INTEGER PRIMARY KEY,\n name TEXT NOT NULL UNIQUE\n )",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "50d891dc85cb19ce33378ced606b10ac982c6fdcd30e6d089a1d140210c9b11a"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR IGNORE INTO tags (name) VALUES (?1)",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "5bbbe0b55b0e8a775165a59c2344bf5cbd7028ca60047a869de25e7931920190"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "CREATE TABLE IF NOT EXISTS tales (\n slug TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT NOT NULL,\n summary TEXT NOT NULL,\n publish_date TEXT NOT NULL,\n content TEXT NOT NULL\n )",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "63586c7d68985fedfb450a942ad03b81fe2f78bb0b317c75d9af6266c6ad6d55"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "CREATE TABLE IF NOT EXISTS adventurers (\n handle TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n profile TEXT NOT NULL,\n image TEXT NOT NULL,\n blurb TEXT NOT NULL\n )",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "67c82ddcbec08a947ef28a28a439bbf3838edfc2e9ce27e6a6661505e5b936e2"
}

View File

@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT id FROM tags WHERE name = ?1",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true
]
},
"hash": "b1fa9c554e3fe18b4117a314c644cc5bf969e512b9fb6b589bd09504317363c0"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "CREATE TABLE IF NOT EXISTS tale_tags (\n tale_slug TEXT,\n tag_id INTEGER,\n FOREIGN KEY(tale_slug) REFERENCES tales(slug),\n FOREIGN KEY(tag_id) REFERENCES tags(id),\n UNIQUE(tale_slug, tag_id)\n )",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "c32d614137f871a3f603c57e99d62b293515e469653452750ed9e5424be00320"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR REPLACE INTO tales (\n slug, title, author, summary, publish_date, content\n ) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "e448c3365fa62303d143b2ed04ee4e230b99d780768c96de7966fbee252e7565"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "CREATE TABLE IF NOT EXISTS tavern (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "ec49fe1746763238c7ead570da9b7800e68e1e7311c16ea07d9e50904b40e817"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT OR REPLACE INTO tavern (key, value) VALUES ('description', ?1)",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "ee6075930ca151fc036d2797b96b29c65de57982428e1a6f45579638b6c7442a"
}

View File

@ -1,24 +1,28 @@
use std::path::{Path, PathBuf};
use chrono::NaiveDate;
use tavern::{Adventurer, Database, FrontMatter, Tale, Tavern};
use tavern::{Adventurer, Database, Legend, Lore, Tale, Tavern};
#[cfg(feature = "publisher")]
fn generate_tavern() -> Tavern
{
let author: Adventurer =
Adventurer { name: String::from("Jason Smith"),
handle: String::from("myrddin"),
let legend: Legend = Legend
{
profile:
String::from("https://cybermages.tech/about/myrddin"),
image:
String::from("https://cybermages.tech/about/myrddin/pic"),
blurb: String::from("I love code!") };
let fm: FrontMatter =
FrontMatter { title: String::from("Test post"),
let author: Adventurer =
Adventurer { name: String::from("Jason Smith"),
handle: String::from("myrddin"),
legend };
let lore: Lore =
Lore { title: String::from("Test post"),
slug: String::from("test_post"),
author: author.handle.clone(),
summary: String::from("The Moon is made of cheese!"),
@ -30,8 +34,8 @@ fn generate_tavern() -> Tavern
10, 41)
.unwrap() };
let tale: Tale = Tale { front_matter: fm,
content: PathBuf::from("posts/test_post.md") };
let tale: Tale = Tale { lore,
story: PathBuf::from("posts/test_post.md") };
// Create a dummy posts directory and file for this example to work

View File

@ -1,6 +1,20 @@
use serde::{Deserialize, Serialize};
///
#[derive(Deserialize, Serialize)]
pub struct Legend
{
/// A link to the adventurer's profile (e.g., personal website, GitHub,
/// etc.).
pub profile: String,
/// A URL or path to an image representing the adventurer (e.g., avatar or
/// portrait).
pub image: String,
/// A short descriptive text or tagline about the adventurer.
pub blurb: String
}
/// Represents an author or contributor of a tale.
///
@ -16,16 +30,9 @@ pub struct Adventurer
/// mentions).
pub handle: String,
/// A link to the adventurer's profile (e.g., personal website, GitHub,
/// etc.).
pub profile: String,
/// A URL or path to an image representing the adventurer (e.g., avatar or
/// portrait).
pub image: String,
/// A short descriptive text or tagline about the adventurer.
pub blurb: String
///
#[serde(flatten)]
pub legend: Legend
}

View File

@ -7,14 +7,14 @@ use sqlx::{Error, Result};
#[cfg(not(feature = "publisher"))]
use sqlx::Row;
use crate::adventurer::Adventurer;
use crate::adventurer::{Adventurer, Legend};
use crate::tale::Tale;
#[cfg(feature = "publisher")]
use crate::converter::Converter;
#[cfg(not(feature = "publisher"))]
use crate::tale::FrontMatter;
use crate::tale::Lore;
@ -136,7 +136,7 @@ impl Database
-> Result<(), Box<dyn std::error::Error>>
{
// Convert the tales content from Markdown to HTML.
let markdown_content = std::fs::read_to_string(&tale.content)?;
let markdown_content = std::fs::read_to_string(&tale.story)?;
let html_content: String = Converter::markdown_to_html(&markdown_content);
// Start a transaction.
@ -147,18 +147,18 @@ impl Database
"INSERT OR REPLACE INTO tales (
slug, title, author, summary, publish_date, content
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
tale.front_matter.slug,
tale.front_matter.title,
tale.front_matter.author,
tale.front_matter.summary,
tale.front_matter.publish_date,
tale.lore.slug,
tale.lore.title,
tale.lore.author,
tale.lore.summary,
tale.lore.publish_date,
html_content
).execute(&mut *tx) // Pass mutable reference to the transaction
.await?;
// Store the tags.
// For each tag ...
for tag_name in &tale.front_matter.tags
for tag_name in &tale.lore.tags
{
// Insert a new tag, ignore if it already exists.
sqlx::query!("INSERT OR IGNORE INTO tags (name) VALUES (?1)",
@ -176,7 +176,7 @@ impl Database
sqlx::query!(
"INSERT OR IGNORE INTO tale_tags (tale_slug, tag_id)
VALUES (?1, ?2)",
tale.front_matter.slug,
tale.lore.slug,
id
).execute(&mut *tx) // Pass mutable reference to the transaction
.await?;
@ -202,9 +202,9 @@ impl Database
) VALUES (?1, ?2, ?3, ?4, ?5)",
adventurer.handle,
adventurer.name,
adventurer.profile,
adventurer.image,
adventurer.blurb
adventurer.legend.profile,
adventurer.legend.image,
adventurer.legend.blurb
).execute(&mut *tx)
.await?;
@ -242,7 +242,7 @@ impl Database
#[cfg(not(feature = "publisher"))]
pub async fn get_tales_summary(&self, categories: &[String])
-> Result<Vec<FrontMatter>>
-> Result<Vec<Lore>>
{
let mut tales = Vec::new();
@ -292,7 +292,7 @@ impl Database
let publish_date = chrono::NaiveDateTime::parse_from_str(&date_str, "%Y-%m-%dT%H:%M:%S")
.map_err(|e| sqlx::Error::Decode(e.into()))?;
tales.push(FrontMatter { title: row.try_get("title")?,
tales.push(Lore { title: row.try_get("title")?,
slug: row.try_get("slug")?,
summary: row.try_get("summary")?,
author: row.try_get("author")?,
@ -336,15 +336,15 @@ impl Database
let publish_date = chrono::NaiveDateTime::parse_from_str(&date_str, "%Y-%m-%dT%H:%M:%S")
.map_err(|e| Error::Decode(e.into()))?;
let front_matter = FrontMatter { title: row.try_get("title")?,
let lore = Lore { title: row.try_get("title")?,
slug: row.try_get("slug")?,
summary: row.try_get("summary")?,
author: row.try_get("author")?,
publish_date,
tags };
Ok(Some(Tale { front_matter,
content: row.try_get("content")? }))
Ok(Some(Tale { lore,
story: row.try_get("content")? }))
}
else
{
@ -358,22 +358,47 @@ impl Database
{
let mut tx = self.pool.begin().await?;
let adventurer = sqlx::query_as!(
Adventurer,
"SELECT
handle AS 'handle!',
name AS 'name!',
profile AS 'profile!',
image AS 'image!',
blurb AS 'blurb!'
FROM adventurers
WHERE handle = ?1",
handle
).fetch_optional(&mut *tx) // Use transaction here
.await?;
let legend = sqlx::query_as!(
Legend,
"SELECT
profile AS profile,
image AS image,
blurb AS blurb
FROM adventurers
WHERE handle = ?1",
handle
)
.fetch_optional(&mut *tx)
.await?;
let hero = sqlx::query!(
"SELECT
name,
handle AS 'handle!'
FROM adventurers
WHERE handle = ?1",
handle
)
.fetch_optional(&mut *tx)
.await?;
tx.commit().await?;
let adventurer = match (hero, legend)
{
(Some(h), Some(l)) =>
{
Some(Adventurer
{
name: h.name,
handle: h.handle,
legend: l,
})
}
_ => { None }
};
Ok(adventurer)
}
}

View File

@ -12,8 +12,8 @@ mod tavern;
pub use crate::adventurer::Adventurer;
pub use crate::adventurer::{Adventurer, Legend};
pub use crate::database::Database;
pub use crate::info::{get_name, get_version};
pub use crate::tale::{FrontMatter, Tale};
pub use crate::tale::{Lore, Tale};
pub use crate::tavern::Tavern;

View File

@ -20,7 +20,7 @@ pub type Markdown = String;
/// This includes details such as the title, author, summary, and
/// associated tags.
#[derive(Deserialize, Serialize)]
pub struct FrontMatter
pub struct Lore
{
/// The title of the tale.
pub title: String,
@ -49,8 +49,8 @@ pub struct Tale
{
/// Metadata of the post.
#[serde(flatten)]
pub front_matter: FrontMatter,
pub lore: Lore,
/// The file path to the Markdown content of the tale.
pub content: Markdown
pub story: Markdown
}