implement account management
This commit is contained in:
parent
dbcac84794
commit
3e249e0a0d
8 changed files with 349 additions and 21 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,3 +2,5 @@
|
|||
data.js
|
||||
fish_data.js
|
||||
beacon.db*
|
||||
.env
|
||||
.sqlx/
|
||||
|
|
173
Cargo.lock
generated
173
Cargo.lock
generated
|
@ -17,6 +17,15 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
|
@ -161,12 +170,15 @@ dependencies = [
|
|||
"chrono",
|
||||
"chrono-humanize",
|
||||
"maud",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -375,7 +387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -943,6 +955,15 @@ version = "0.4.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
|
@ -1025,6 +1046,25 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoid"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.4"
|
||||
|
@ -1087,6 +1127,12 @@ version = "1.20.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
|
@ -1248,6 +1294,50 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
|
@ -1299,7 +1389,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1436,6 +1526,15 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
@ -1760,7 +1859,7 @@ dependencies = [
|
|||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1783,6 +1882,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
|
@ -1944,6 +2053,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2014,6 +2153,12 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
|
@ -2124,6 +2269,28 @@ dependencies = [
|
|||
"wasite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -9,6 +9,7 @@ axum = { version = "0.8.1", features = ["macros"] }
|
|||
chrono = "0.4.39"
|
||||
chrono-humanize = "0.2.3"
|
||||
maud = { version = "0.27.0", features = ["axum"] }
|
||||
nanoid = "0.4.0"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde_json = "1.0.138"
|
||||
serde_path_to_error = "0.1.16"
|
||||
|
@ -16,6 +17,9 @@ sqlx = { version = "0.8", features = [
|
|||
"runtime-tokio",
|
||||
"tls-rustls-ring-webpki",
|
||||
"sqlite",
|
||||
"migrate",
|
||||
] }
|
||||
tokio = { version = "1.43.0", features = ["full"] }
|
||||
tower-http = { version = "0.6.2", features = ["fs"] }
|
||||
tower-http = { version = "0.6.2", features = ["fs", "trace"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
|
|
11
migrations/20250206105906_initial.sql
Normal file
11
migrations/20250206105906_initial.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- Add migration script here
|
||||
CREATE TABLE accounts (account_id VARCHAR(10) PRIMARY KEY);
|
||||
|
||||
CREATE TABLE caught_fish (
|
||||
id INTEGER PRIMARY KEY,
|
||||
account_id VARCHAR(10),
|
||||
fish_id INTEGER,
|
||||
FOREIGN KEY (account_id) REFERENCES accounts (account_id) ON DELETE CASCADE ON UPDATE NO ACTION
|
||||
);
|
||||
|
||||
CREATE INDEX fish_accounts ON caught_fish (account_id);
|
32
src/db.rs
Normal file
32
src/db.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::AppError;
|
||||
|
||||
pub struct Account {
|
||||
account_id: Option<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?;
|
||||
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)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
if results.len() == 1 {
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
69
src/main.rs
69
src/main.rs
|
@ -1,13 +1,22 @@
|
|||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, routing::get, Router};
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use data::Data;
|
||||
use maud::Markup;
|
||||
use maud::{html, Markup};
|
||||
use nanoid::nanoid;
|
||||
use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite};
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_http::{services::ServeDir, trace::TraceLayer};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
pub mod clock;
|
||||
pub mod data;
|
||||
pub mod db;
|
||||
pub mod forecast;
|
||||
pub mod templates;
|
||||
|
||||
|
@ -37,21 +46,67 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn root_handler() -> Result<Markup, AppError> {
|
||||
Ok(templates::root())
|
||||
}
|
||||
|
||||
async fn new_account_handler(state: State<Arc<AppState>>) -> Result<Markup, AppError> {
|
||||
let id = nanoid!(10);
|
||||
db::insert_account(&id, &state.pool).await?;
|
||||
Ok(templates::new_account(id))
|
||||
}
|
||||
|
||||
async fn main_handler(state: State<Arc<AppState>>) -> Result<Markup, AppError> {
|
||||
Ok(templates::main_page(state, true))
|
||||
Ok(templates::main_page(state))
|
||||
}
|
||||
|
||||
async fn to_handler(
|
||||
state: State<Arc<AppState>>,
|
||||
query: Query<HashMap<String, String>>,
|
||||
) -> Result<Response, AppError> {
|
||||
let rejection_html = html! {
|
||||
p { "The provided account ID doesn't exist. " }
|
||||
};
|
||||
if let Some(id) = query.get("id") {
|
||||
let exists = db::get_account(id, &state.pool).await?;
|
||||
if exists {
|
||||
Ok(Redirect::to(&format!("/{}", id)).into_response())
|
||||
} else {
|
||||
Ok(rejection_html.into_response())
|
||||
}
|
||||
} else {
|
||||
Ok(rejection_html.into_response())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
format!(
|
||||
"{}=debug,tower_http=debug,axum::rejection=trace",
|
||||
env!("CARGO_CRATE_NAME")
|
||||
)
|
||||
.into()
|
||||
}),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(10)
|
||||
.connect("sqlite://beacon.db")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
sqlx::migrate!("./migrations").run(&pool).await.unwrap();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(main_handler))
|
||||
.route("/", get(root_handler).post(new_account_handler))
|
||||
.route("/to", get(to_handler))
|
||||
.route("/{id}", get(main_handler))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
.with_state(Arc::new(AppState {
|
||||
data: Data::new(),
|
||||
|
|
|
@ -21,7 +21,7 @@ pub fn layout(content: Markup) -> Markup {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn main_page(state: State<Arc<AppState>>, with_layout: bool) -> Markup {
|
||||
pub fn main_page(state: State<Arc<AppState>>) -> Markup {
|
||||
let meta = state.data.fish_with_meta();
|
||||
let mut values: Vec<&CombinedFish> = meta.values().filter(|f| f.entry.big_fish).collect();
|
||||
values.sort_by(|afish, bfish| {
|
||||
|
@ -109,13 +109,64 @@ pub fn main_page(state: State<Arc<AppState>>, with_layout: bool) -> Markup {
|
|||
}
|
||||
};
|
||||
|
||||
if with_layout {
|
||||
layout(html! {
|
||||
main hx-get="/" hx-trigger="every 10s" {
|
||||
(template)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
template
|
||||
}
|
||||
layout(html! {
|
||||
main hx-get="" hx-trigger="every 10s" {
|
||||
(template)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn root() -> Markup {
|
||||
layout(html! {
|
||||
h1 { "Beacon" }
|
||||
p {
|
||||
"Beacon is a website to track progress for collecting " i { "big fish" } " in Final Fantasy XIV. "
|
||||
"To use it, you need an " b { "account ID" } ", which serves to remember which fish you've caught. "
|
||||
"If you have an account ID, you can enter it here, or if you don't, create a new one. Usually, your "
|
||||
"browser will remember your ID and automatically redirect you, though."
|
||||
}
|
||||
|
||||
form method="post" {
|
||||
button type="submit" { "Generate a new account ID" }
|
||||
}
|
||||
|
||||
p { "or" }
|
||||
|
||||
form method="get" action="/to" {
|
||||
input name="id" type="text" placeholder="Enter your account ID...";
|
||||
button type="submit" { "Go to tracker" }
|
||||
}
|
||||
|
||||
h2 { "What is this about?" }
|
||||
p {
|
||||
"In Final Fantasy XIV, you can catch a number of elusive and rare fish called big fish. These can be quite tricky to even find -- most of them have a specific time window and only appear during certain weather patterns. This website helps with keeping track of all of that."
|
||||
}
|
||||
|
||||
h2 { "Why not use the other tracker website?" }
|
||||
p {
|
||||
"By all means! " a href="https://ff14fish.carbuncleplushy.com" { "The original tracker" } " is a great resource, and functionally superior to this website. "
|
||||
"I mostly created this because of two reasons:"
|
||||
}
|
||||
ol {
|
||||
li { "Everything is rendered on the server - meaning it doesn't eat away at your system resources (good for laptop players!)" }
|
||||
li { "Your caught fish are synchronized across devices, and data doesn't get deleted when you clear your browser data" }
|
||||
}
|
||||
p {
|
||||
"These were the two main reasons why I created this. This website couldn't exist without Carbuncle Plushy's tracker -- it uses the same data "
|
||||
"that has been painstakingly compiled by hand! If you can, please support them."
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_account(id: String) -> Markup {
|
||||
layout(html! {
|
||||
p {
|
||||
"Your new account ID has been created. Plase save it in a place you remember, like a password manager. If you lose it, you will be unable to "
|
||||
"access your account."
|
||||
}
|
||||
|
||||
h1 { (id) }
|
||||
|
||||
a href=(format!("/{}", id)) { "Click here to proceed to the tracker" }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -96,3 +96,9 @@ section.alwaysup {
|
|||
.catchpath:has(.tug) {
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
max-width: 50em;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue