move to postgres; implement marking caught fish

This commit is contained in:
insects 2025-02-06 14:25:51 +01:00
parent 3e249e0a0d
commit e564e87c94
7 changed files with 110 additions and 34 deletions

View file

@ -1,26 +1,26 @@
use sqlx::{Pool, Sqlite};
use sqlx::{Pool, Postgres};
use crate::AppError;
pub struct Account {
account_id: Option<String>,
pub account_id: String,
}
pub async fn insert_account(id: &str, pool: &Pool<Sqlite>) -> Result<(), AppError> {
sqlx::query!(
"
INSERT INTO accounts (account_id)
VALUES (?)
",
id
)
.execute(pool)
.await?;
pub struct CaughtFish {
pub id: Option<i32>,
pub account_id: String,
pub fish_id: i32,
}
pub async fn insert_account(id: &str, pool: &Pool<Postgres>) -> Result<(), AppError> {
sqlx::query!("INSERT INTO accounts (account_id) VALUES ($1)", id)
.execute(pool)
.await?;
Ok(())
}
pub async fn get_account(id: &str, pool: &Pool<Sqlite>) -> Result<bool, AppError> {
let results = sqlx::query_as!(Account, "SELECT * FROM accounts WHERE account_id = ?", id)
pub async fn get_account(id: &str, pool: &Pool<Postgres>) -> Result<bool, AppError> {
let results = sqlx::query_as!(Account, "SELECT * FROM accounts WHERE account_id = $1", id)
.fetch_all(pool)
.await?;
@ -30,3 +30,33 @@ pub async fn get_account(id: &str, pool: &Pool<Sqlite>) -> Result<bool, AppError
Ok(false)
}
}
pub async fn insert_caught_fish(
acc_id: &str,
fish_id: &i32,
pool: &Pool<Postgres>,
) -> Result<(), AppError> {
sqlx::query!(
"
INSERT INTO caught_fish (account_id, fish_id) VALUES ($1, $2)
",
acc_id,
fish_id
)
.execute(pool)
.await?;
Ok(())
}
pub async fn get_caught_fish(acc_id: &str, pool: &Pool<Postgres>) -> Result<Vec<i32>, AppError> {
let results = sqlx::query_as!(
CaughtFish,
"SELECT * FROM caught_fish WHERE account_id = $1",
acc_id
)
.fetch_all(pool)
.await?;
Ok(results.iter().map(|cf| cf.fish_id).collect())
}

View file

@ -1,16 +1,16 @@
use std::{collections::HashMap, sync::Arc};
use axum::{
extract::{Query, State},
extract::{Path, Query, State},
http::StatusCode,
response::{IntoResponse, Redirect, Response},
routing::get,
routing::{get, post},
Router,
};
use data::Data;
use maud::{html, Markup};
use nanoid::nanoid;
use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite};
use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
use tower_http::{services::ServeDir, trace::TraceLayer};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@ -22,7 +22,7 @@ pub mod templates;
pub struct AppState {
pub data: Data,
pub pool: Pool<Sqlite>,
pub pool: Pool<Postgres>,
}
pub struct AppError(anyhow::Error);
@ -56,8 +56,30 @@ async fn new_account_handler(state: State<Arc<AppState>>) -> Result<Markup, AppE
Ok(templates::new_account(id))
}
async fn main_handler(state: State<Arc<AppState>>) -> Result<Markup, AppError> {
Ok(templates::main_page(state))
async fn main_handler(
state: State<Arc<AppState>>,
Path(acc_id): Path<String>,
) -> Result<Response, AppError> {
let exists = db::get_account(&acc_id, &state.pool).await?;
if !exists {
return Ok(html! { "The provided account ID doesn't eixst." }.into_response());
}
let caught_fish = db::get_caught_fish(&acc_id, &state.pool).await?;
Ok(templates::main_page(state, caught_fish, acc_id).into_response())
}
async fn insert_cf_handler(
state: State<Arc<AppState>>,
Path((acc_id, fish_id)): Path<(String, String)>,
) -> Result<Response, AppError> {
let exists = db::get_account(&acc_id, &state.pool).await?;
if !exists {
return Ok(html! { "The provided account ID doesn't eixst." }.into_response());
}
db::insert_caught_fish(&acc_id, &fish_id.parse::<i32>().unwrap(), &state.pool).await?;
Ok(Redirect::to(&format!("/{}", acc_id)).into_response())
}
async fn to_handler(
@ -81,6 +103,9 @@ async fn to_handler(
#[tokio::main]
async fn main() {
#[cfg(debug_assertions)]
dotenvy::dotenv().unwrap();
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
@ -94,9 +119,9 @@ async fn main() {
.with(tracing_subscriber::fmt::layer())
.init();
let pool = SqlitePoolOptions::new()
let pool = PgPoolOptions::new()
.max_connections(10)
.connect("sqlite://beacon.db")
.connect(&std::env::var("DATABASE_URL").unwrap())
.await
.unwrap();
@ -106,6 +131,7 @@ async fn main() {
.route("/", get(root_handler).post(new_account_handler))
.route("/to", get(to_handler))
.route("/{id}", get(main_handler))
.route("/{acc_id}/catch/{fish_id}", post(insert_cf_handler))
.layer(TraceLayer::new_for_http())
.nest_service("/static", ServeDir::new("static"))
.with_state(Arc::new(AppState {

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use axum::extract::State;
use maud::{html, Markup, DOCTYPE};
use maud::{html, Markup, PreEscaped, DOCTYPE};
use crate::{clock, data::CombinedFish, AppState};
@ -21,9 +21,16 @@ pub fn layout(content: Markup) -> Markup {
}
}
pub fn main_page(state: State<Arc<AppState>>) -> Markup {
pub fn main_page(state: State<Arc<AppState>>, caught_fish: Vec<i32>, acc_id: String) -> Markup {
let meta = state.data.fish_with_meta();
let mut values: Vec<&CombinedFish> = meta.values().filter(|f| f.entry.big_fish).collect();
let mut values: Vec<&CombinedFish> = meta
.values()
.filter(|f| {
let is_big_fish = f.entry.big_fish;
let is_caught = caught_fish.contains(&(f.entry.id as i32));
is_big_fish && !is_caught
})
.collect();
values.sort_by(|afish, bfish| {
bfish
.is_up
@ -50,9 +57,16 @@ pub fn main_page(state: State<Arc<AppState>>) -> Markup {
@for fish in values {
section.up[fish.is_up].alwaysup[fish.is_always_up] {
.title {
h3 { (fish.meta.name_en) }
.subtitle {
"Patch " (fish.entry.patch)
div {
form action=(format!("/{}/catch/{}", acc_id, fish.entry.id)) method="post" {
button.catch-button type="submit" { (PreEscaped("&check;")) }
}
}
div {
h3 { (fish.meta.name_en) }
.subtitle {
"Patch " (fish.entry.patch)
}
}
}
.when {