implement account management
This commit is contained in:
parent
dbcac84794
commit
3e249e0a0d
8 changed files with 349 additions and 21 deletions
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" }
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue