added sqlx offline cache and switched to query_as!

This commit is contained in:
Butter 2026-05-04 12:10:33 -04:00
parent e0369b50dd
commit 8f2cb6eda6
9 changed files with 1682 additions and 28 deletions

2
.env
View File

@ -1,2 +0,0 @@
DATABASE_URL=sqlite:./sarmentine.db
SESSION_SECRET=gzC0+m1ol9sPHaZpEU/AwIQVA9STkmxrWK6HhCI7ovlNdVzQqR0sg8VHQCRxURQFmVNEiQvJ8cx7G7nNd3b4jw

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
/target /target
sarmentine.db* sarmentine.db*
.env
.env.*

View File

@ -0,0 +1,62 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!\", username, email, password, bio, avatar_url, role, created_at\n FROM users WHERE email = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "username",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "email",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "password",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "bio",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "avatar_url",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "role",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 7,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
false,
true,
true,
false,
false
]
},
"hash": "59b62edeaea0297e51ae3d1d973f46cc1a0786ad5f86bf5f539d12f728136f1d"
}

View File

@ -0,0 +1,62 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!\", username, email, password, bio, avatar_url, role, created_at\n FROM users WHERE id = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "username",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "email",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "password",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "bio",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "avatar_url",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "role",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 7,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
true,
true,
false,
false
]
},
"hash": "9496103101618f59b785c0c8d2cee4f6053055cb06db77dc8033c7e31d503a77"
}

View File

@ -0,0 +1,62 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!\", username, email, password, bio, avatar_url, role, created_at\n FROM users WHERE username = ?",
"describe": {
"columns": [
{
"name": "id!",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "username",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "email",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "password",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "bio",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "avatar_url",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "role",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 7,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
false,
true,
true,
false,
false
]
},
"hash": "d12d0743a4bbe18465adb69102e3f7dc70ac567c7610cc9aa6f9c6bd84be6a6b"
}

1475
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ tokio = { version = "1", features = ["full"] }
askama = { version = "0.12", features = ["with-axum"] } askama = { version = "0.12", features = ["with-axum"] }
askama_axum = "0.4" askama_axum = "0.4"
tower-http = { version = "0.5", features = ["fs"] } tower-http = { version = "0.5", features = ["fs"] }
sqlx = { version = "0.7", features = ["runtime-tokio","chrono", "sqlite", "migrate"] } sqlx = { version = "0.7", features = ["runtime-tokio", "chrono", "sqlite", "migrate"] }
dotenvy = "0.15" dotenvy = "0.15"
argon2 = "0.5" argon2 = "0.5"
rand_core = { version = "0.6", features = ["std"] } rand_core = { version = "0.6", features = ["std"] }
@ -19,3 +19,6 @@ serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
# Error handling # Error handling
anyhow = "1" anyhow = "1"
[dev-dependencies]
cargo-generate-rpm = "0.20"

View File

@ -51,7 +51,7 @@ async fn index(
async fn main() { async fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let database_url = std::env::var("DATABASE_URL") let database_url = std::env::var("SARMENTINE_DATABASE_URL")
.unwrap_or_else(|_| "sqlite:./sarmentine.db".into()); .unwrap_or_else(|_| "sqlite:./sarmentine.db".into());
let pool: SqlitePool = db::connect(&database_url).await; let pool: SqlitePool = db::connect(&database_url).await;
@ -59,7 +59,7 @@ async fn main() {
let session_store = SqliteStore::new(pool.clone()); let session_store = SqliteStore::new(pool.clone());
session_store.migrate().await.expect("session store migration failed"); session_store.migrate().await.expect("session store migration failed");
let session_secret = std::env::var("SESSION_SECRET") let session_secret = std::env::var("SARMENTINE_SESSION_SECRET")
.unwrap_or_else(|_| "change-me-in-production-use-a-long-random-string!!".into()); .unwrap_or_else(|_| "change-me-in-production-use-a-long-random-string!!".into());
let session_layer = SessionManagerLayer::new(session_store) let session_layer = SessionManagerLayer::new(session_store)
@ -69,6 +69,9 @@ async fn main() {
session_secret.as_bytes(), session_secret.as_bytes(),
)); ));
let static_dir = std::env::var("SARMENTINE_STATIC_DIR")
.unwrap_or_else(|_| "static".into());
let app = Router::new() let app = Router::new()
.route("/", get(index)) .route("/", get(index))
.route("/auth/register", get(register_page)) .route("/auth/register", get(register_page))
@ -76,7 +79,7 @@ async fn main() {
.route("/auth/login", get(login_page)) .route("/auth/login", get(login_page))
.route("/auth/login", post(login_submit)) .route("/auth/login", post(login_submit))
.route("/auth/logout", post(logout)) .route("/auth/logout", post(logout))
.nest_service("/static", ServeDir::new("static")) .nest_service("/static", ServeDir::new(&static_dir))
.layer(session_layer) .layer(session_layer)
.with_state(pool); .with_state(pool);

View File

@ -1,8 +1,8 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{FromRow, SqlitePool}; use sqlx::SqlitePool;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User { pub struct User {
pub id: i64, pub id: i64,
pub username: String, pub username: String,
@ -17,31 +17,34 @@ pub struct User {
impl User { impl User {
pub async fn find_by_id(pool: &SqlitePool, id: i64) -> sqlx::Result<Option<User>> { pub async fn find_by_id(pool: &SqlitePool, id: i64) -> sqlx::Result<Option<User>> {
sqlx::query_as( sqlx::query_as!(
"SELECT id, username, email, password, bio, avatar_url, role, created_at User,
FROM users WHERE id = ?" r#"SELECT id AS "id!", username, email, password, bio, avatar_url, role, created_at
FROM users WHERE id = ?"#,
id
) )
.bind(id)
.fetch_optional(pool) .fetch_optional(pool)
.await .await
} }
pub async fn find_by_username(pool: &SqlitePool, username: &str) -> sqlx::Result<Option<User>> { pub async fn find_by_username(pool: &SqlitePool, username: &str) -> sqlx::Result<Option<User>> {
sqlx::query_as( sqlx::query_as!(
"SELECT id, username, email, password, bio, avatar_url, role, created_at User,
FROM users WHERE username = ?" r#"SELECT id AS "id!", username, email, password, bio, avatar_url, role, created_at
FROM users WHERE username = ?"#,
username
) )
.bind(username)
.fetch_optional(pool) .fetch_optional(pool)
.await .await
} }
pub async fn find_by_email(pool: &SqlitePool, email: &str) -> sqlx::Result<Option<User>> { pub async fn find_by_email(pool: &SqlitePool, email: &str) -> sqlx::Result<Option<User>> {
sqlx::query_as( sqlx::query_as!(
"SELECT id, username, email, password, bio, avatar_url, role, created_at User,
FROM users WHERE email = ?" r#"SELECT id AS "id!", username, email, password, bio, avatar_url, role, created_at
FROM users WHERE email = ?"#,
email
) )
.bind(email)
.fetch_optional(pool) .fetch_optional(pool)
.await .await
} }