implement support for pinning
This commit is contained in:
parent
9882ab74a5
commit
67709ac241
5 changed files with 117 additions and 5 deletions
9
migrations/20250210231615_add_pinned.sql
Normal file
9
migrations/20250210231615_add_pinned.sql
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
-- Add migration script here
|
||||||
|
CREATE TABLE pinned_fish (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
account_id VARCHAR(10) NOT NULL,
|
||||||
|
fish_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (account_id) REFERENCES accounts (account_id) ON DELETE CASCADE ON UPDATE NO ACTION
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX pinned_fish_accounts ON pinned_fish (account_id);
|
49
src/db.rs
49
src/db.rs
|
@ -14,6 +14,13 @@ pub struct CaughtFish {
|
||||||
pub fish_id: i32,
|
pub fish_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
pub struct PinnedFish {
|
||||||
|
pub id: Option<i32>,
|
||||||
|
pub account_id: String,
|
||||||
|
pub fish_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert_account(id: &str, pool: &Pool<Postgres>) -> Result<(), AppError> {
|
pub async fn insert_account(id: &str, pool: &Pool<Postgres>) -> Result<(), AppError> {
|
||||||
sqlx::query("INSERT INTO accounts (account_id) VALUES ($1)")
|
sqlx::query("INSERT INTO accounts (account_id) VALUES ($1)")
|
||||||
.bind(id)
|
.bind(id)
|
||||||
|
@ -62,3 +69,45 @@ pub async fn get_caught_fish(acc_id: &str, pool: &Pool<Postgres>) -> Result<Vec<
|
||||||
|
|
||||||
Ok(results.iter().map(|cf| cf.fish_id).collect())
|
Ok(results.iter().map(|cf| cf.fish_id).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn insert_pinned_fish(
|
||||||
|
acc_id: &str,
|
||||||
|
fish_id: &i32,
|
||||||
|
pool: &Pool<Postgres>,
|
||||||
|
) -> Result<(), AppError> {
|
||||||
|
sqlx::query(
|
||||||
|
"
|
||||||
|
INSERT INTO pinned_fish (account_id, fish_id) VALUES ($1, $2)
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(acc_id)
|
||||||
|
.bind(fish_id)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_pinned_fish(acc_id: &str, pool: &Pool<Postgres>) -> Result<Vec<i32>, AppError> {
|
||||||
|
let results: Vec<PinnedFish> =
|
||||||
|
sqlx::query_as("SELECT * FROM pinned_fish WHERE account_id = $1")
|
||||||
|
.bind(acc_id)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(results.iter().map(|cf| cf.fish_id).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_pinned_fish(
|
||||||
|
acc_id: &str,
|
||||||
|
fish_id: &i32,
|
||||||
|
pool: &Pool<Postgres>,
|
||||||
|
) -> Result<(), AppError> {
|
||||||
|
sqlx::query("DELETE FROM pinned_fish WHERE account_id = $1 AND fish_id = $2")
|
||||||
|
.bind(acc_id)
|
||||||
|
.bind(fish_id)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -8,6 +8,7 @@ use axum::{
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use data::{CombinedFish, Data, Filters};
|
use data::{CombinedFish, Data, Filters};
|
||||||
|
use db::delete_pinned_fish;
|
||||||
use maud::{html, Markup};
|
use maud::{html, Markup};
|
||||||
use nanoid::nanoid;
|
use nanoid::nanoid;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -77,6 +78,7 @@ async fn main_handler(
|
||||||
return Ok(html! { "The provided account ID doesn't eixst." }.into_response());
|
return Ok(html! { "The provided account ID doesn't eixst." }.into_response());
|
||||||
}
|
}
|
||||||
let caught_fish = db::get_caught_fish(&acc_id, &state.pool).await?;
|
let caught_fish = db::get_caught_fish(&acc_id, &state.pool).await?;
|
||||||
|
let pinned_fish = db::get_pinned_fish(&acc_id, &state.pool).await?;
|
||||||
let filters = Filters::from_query(query);
|
let filters = Filters::from_query(query);
|
||||||
|
|
||||||
// If this is a HTMX-sent request, don't resend the entire page, just the list.
|
// If this is a HTMX-sent request, don't resend the entire page, just the list.
|
||||||
|
@ -86,9 +88,10 @@ async fn main_handler(
|
||||||
let meta = state.data.fish_with_meta();
|
let meta = state.data.fish_with_meta();
|
||||||
let mut values: Vec<&CombinedFish> = filters.filter(meta.values().collect(), &caught_fish);
|
let mut values: Vec<&CombinedFish> = filters.filter(meta.values().collect(), &caught_fish);
|
||||||
values.sort_by(|afish, bfish| {
|
values.sort_by(|afish, bfish| {
|
||||||
bfish
|
pinned_fish
|
||||||
.is_up
|
.contains(&(bfish.entry.id as i32))
|
||||||
.cmp(&afish.is_up)
|
.cmp(&pinned_fish.contains(&(afish.entry.id as i32)))
|
||||||
|
.then(bfish.is_up.cmp(&afish.is_up))
|
||||||
.then(bfish.is_always_up.cmp(&afish.is_always_up))
|
.then(bfish.is_always_up.cmp(&afish.is_always_up))
|
||||||
.then(bfish.rarity.total_cmp(&afish.rarity).reverse())
|
.then(bfish.rarity.total_cmp(&afish.rarity).reverse())
|
||||||
.then(bfish.meta.name_en.cmp(&afish.meta.name_en))
|
.then(bfish.meta.name_en.cmp(&afish.meta.name_en))
|
||||||
|
@ -100,6 +103,7 @@ async fn main_handler(
|
||||||
state,
|
state,
|
||||||
fish: values,
|
fish: values,
|
||||||
caught_fish,
|
caught_fish,
|
||||||
|
pinned_fish,
|
||||||
acc_id,
|
acc_id,
|
||||||
filters,
|
filters,
|
||||||
only_list: is_htmx,
|
only_list: is_htmx,
|
||||||
|
@ -121,6 +125,34 @@ async fn insert_cf_handler(
|
||||||
Ok(Redirect::to(&format!("/{}", acc_id)).into_response())
|
Ok(Redirect::to(&format!("/{}", acc_id)).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn insert_pin_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 exist." }.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
|
db::insert_pinned_fish(&acc_id, &fish_id.parse::<i32>()?, &state.pool).await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to(&format!("/{}", acc_id)).into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_pin_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 exist." }.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
|
db::delete_pinned_fish(&acc_id, &fish_id.parse::<i32>()?, &state.pool).await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to(&format!("/{}", acc_id)).into_response())
|
||||||
|
}
|
||||||
|
|
||||||
async fn to_handler(
|
async fn to_handler(
|
||||||
state: State<Arc<AppState>>,
|
state: State<Arc<AppState>>,
|
||||||
query: Query<HashMap<String, String>>,
|
query: Query<HashMap<String, String>>,
|
||||||
|
@ -171,6 +203,8 @@ async fn main() {
|
||||||
.route("/to", get(to_handler))
|
.route("/to", get(to_handler))
|
||||||
.route("/{id}", get(main_handler))
|
.route("/{id}", get(main_handler))
|
||||||
.route("/{acc_id}/catch/{fish_id}", post(insert_cf_handler))
|
.route("/{acc_id}/catch/{fish_id}", post(insert_cf_handler))
|
||||||
|
.route("/{acc_id}/pin/{fish_id}", post(insert_pin_handler))
|
||||||
|
.route("/{acc_id}/pin/{fish_id}/delete", post(delete_pin_handler))
|
||||||
.route("/changelog", get(|| async { changelog::changelog_page() }))
|
.route("/changelog", get(|| async { changelog::changelog_page() }))
|
||||||
.route(
|
.route(
|
||||||
"/logout",
|
"/logout",
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub struct ViewData<'a> {
|
||||||
pub state: State<Arc<AppState>>,
|
pub state: State<Arc<AppState>>,
|
||||||
pub fish: Vec<&'a CombinedFish<'a>>,
|
pub fish: Vec<&'a CombinedFish<'a>>,
|
||||||
pub caught_fish: Vec<i32>,
|
pub caught_fish: Vec<i32>,
|
||||||
|
pub pinned_fish: Vec<i32>,
|
||||||
pub acc_id: String,
|
pub acc_id: String,
|
||||||
pub filters: Filters,
|
pub filters: Filters,
|
||||||
pub only_list: bool,
|
pub only_list: bool,
|
||||||
|
@ -74,7 +75,7 @@ pub fn main_page(data: ViewData) -> Markup {
|
||||||
pub fn fish_list(data: &ViewData) -> Markup {
|
pub fn fish_list(data: &ViewData) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
@for fish in data.fish.clone() {
|
@for fish in data.fish.clone() {
|
||||||
section.fish.up[fish.is_up].alwaysup[fish.is_always_up] {
|
section.fish.up[fish.is_up].alwaysup[fish.is_always_up].pinned[data.pinned_fish.contains(&(fish.entry.id as i32))] {
|
||||||
.title {
|
.title {
|
||||||
div {
|
div {
|
||||||
@if !data.caught_fish.contains(&(fish.entry.id as i32)) {
|
@if !data.caught_fish.contains(&(fish.entry.id as i32)) {
|
||||||
|
@ -82,6 +83,16 @@ pub fn fish_list(data: &ViewData) -> Markup {
|
||||||
button.catch-button type="submit" { (PreEscaped("✓")) }
|
button.catch-button type="submit" { (PreEscaped("✓")) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if !data.pinned_fish.contains(&(fish.entry.id as i32)) {
|
||||||
|
form action=(format!("/{}/pin/{}", data.acc_id, fish.entry.id)) method="post" {
|
||||||
|
button.pin-button type="submit" { (PreEscaped("☆"))}
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
form action=(format!("/{}/pin/{}/delete", data.acc_id, fish.entry.id)) method="post" {
|
||||||
|
button.pin-button type="submit" { (PreEscaped("★"))}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
h3 {
|
h3 {
|
||||||
|
@ -165,8 +176,8 @@ pub fn fish_list(data: &ViewData) -> Markup {
|
||||||
|
|
||||||
@if fish.entry.start_hour.is_some() && fish.entry.end_hour.is_some() {
|
@if fish.entry.start_hour.is_some() && fish.entry.end_hour.is_some() {
|
||||||
div {
|
div {
|
||||||
"ET "
|
|
||||||
@if !fish.is_always_up {
|
@if !fish.is_always_up {
|
||||||
|
"ET "
|
||||||
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.start_hour.unwrap())))
|
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.start_hour.unwrap())))
|
||||||
"-"
|
"-"
|
||||||
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.end_hour.unwrap())))
|
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.end_hour.unwrap())))
|
||||||
|
|
|
@ -210,3 +210,12 @@ h2 small {
|
||||||
color: gray;
|
color: gray;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.pinned {
|
||||||
|
background-color: tomato !important;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pinned *:not(button) {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue