add `/<uuid> route + backend handling
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
use sqlx::SqlitePool;
|
||||
|
36
src/game.rs
36
src/game.rs
@@ -1,7 +1,7 @@
|
||||
use crate::{client_id, page::new, Backend};
|
||||
use crate::{page::new, Backend};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::{IntoResponse, Response},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
@@ -11,9 +11,9 @@ use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
async fn index(State(backend): State<Arc<Backend>>, cookies: CookieJar) -> Response {
|
||||
let (cookies, client) = client_id(cookies);
|
||||
let (cookies, client) = backend.client(cookies).await;
|
||||
|
||||
let sightings = backend.sightings_for_user_uuid(&client).await;
|
||||
let sightings = backend.sightings_for_client(&client).await;
|
||||
let amount_total_cameras = backend.amount_total_cameras().await;
|
||||
|
||||
let markup = new(html! {
|
||||
@@ -44,25 +44,25 @@ async fn index(State(backend): State<Arc<Backend>>, cookies: CookieJar) -> Respo
|
||||
(cookies, markup).into_response()
|
||||
}
|
||||
|
||||
async fn game(cookies: CookieJar, Path(uuid): Path<String>) -> Response {
|
||||
let (cookies, client) = client_id(cookies);
|
||||
async fn game(
|
||||
State(backend): State<Arc<Backend>>,
|
||||
cookies: CookieJar,
|
||||
Path(uuid): Path<String>,
|
||||
) -> Result<Redirect, Response> {
|
||||
let (cookies, client) = backend.client(cookies).await;
|
||||
|
||||
let Ok(uuid) = Uuid::parse_str(&uuid) else {
|
||||
return not_found().await.into_response();
|
||||
return Err(not_found().await.into_response());
|
||||
};
|
||||
|
||||
let markup = new(html! {
|
||||
hgroup {
|
||||
h1 { "Digital Shadows" (PreEscaped("—")) "Who finds the most cameras?" }
|
||||
h2 {
|
||||
(client)
|
||||
" found camera "
|
||||
(uuid)
|
||||
}
|
||||
}
|
||||
});
|
||||
let Some(camera) = backend.camera_by_uuid(uuid).await else {
|
||||
return Err(not_found().await.into_response());
|
||||
};
|
||||
|
||||
(cookies, markup).into_response()
|
||||
let succ = backend.client_found_camera(&client, &camera).await;
|
||||
// TODO: show succ/err based on succ
|
||||
|
||||
Ok(Redirect::to("/game"))
|
||||
}
|
||||
|
||||
async fn not_found() -> Markup {
|
||||
|
27
src/main.rs
27
src/main.rs
@@ -1,3 +1,4 @@
|
||||
use crate::model::client::Client;
|
||||
use axum::{routing::get, Router};
|
||||
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||
use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, SqlitePool};
|
||||
@@ -14,19 +15,21 @@ pub(crate) enum Backend {
|
||||
Sqlite(SqlitePool),
|
||||
}
|
||||
|
||||
fn client_id(cookies: CookieJar) -> (CookieJar, String) {
|
||||
let mut cookies = cookies;
|
||||
if cookies.get("client_id").is_none() {
|
||||
let id = Uuid::new_v4().to_string();
|
||||
cookies = cookies.add(Cookie::new("client_id", id))
|
||||
impl Backend {
|
||||
async fn client(&self, cookies: CookieJar) -> (CookieJar, Client) {
|
||||
let existing_uuid = cookies
|
||||
.get("client_id")
|
||||
.and_then(|cookie| Uuid::parse_str(cookie.value()).ok());
|
||||
|
||||
match existing_uuid {
|
||||
Some(uuid) => (cookies, self.get_client(&uuid).await),
|
||||
None => {
|
||||
let new_id = Uuid::new_v4();
|
||||
let updated_cookies = cookies.add(Cookie::new("client_id", new_id.to_string()));
|
||||
(updated_cookies, self.get_client(&new_id).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id = cookies
|
||||
.get("client_id")
|
||||
.expect("can't happen, as we checked above")
|
||||
.to_string();
|
||||
|
||||
(cookies, id)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@@ -1,15 +1,30 @@
|
||||
use crate::Backend;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||
pub struct Camera {
|
||||
pub uuid: String,
|
||||
pub desc: String,
|
||||
pub desc: Option<String>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub(crate) async fn camera_by_uuid(&self, uuid: Uuid) -> Option<Camera> {
|
||||
let uuid = uuid.to_string();
|
||||
match self {
|
||||
Backend::Sqlite(db) => sqlx::query_as!(
|
||||
Camera,
|
||||
"SELECT uuid, desc, name FROM camera WHERE uuid = ?",
|
||||
uuid
|
||||
)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn amount_total_cameras(&self) -> i64 {
|
||||
match self {
|
||||
Backend::Sqlite(db) => {
|
||||
|
29
src/model/client.rs
Normal file
29
src/model/client.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::Backend;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||
pub struct Client {
|
||||
pub uuid: String,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub(crate) async fn get_client(&self, uuid: &Uuid) -> Client {
|
||||
let uuid = uuid.to_string();
|
||||
|
||||
match self {
|
||||
Backend::Sqlite(db) => {
|
||||
sqlx::query!("INSERT OR IGNORE INTO client (uuid) VALUES (?);", uuid)
|
||||
.execute(db)
|
||||
.await
|
||||
.unwrap();
|
||||
sqlx::query_as!(Client, "SELECT uuid, name FROM client WHERE uuid = ?", uuid)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.expect("we assured that uuid exists in previous query")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,3 @@
|
||||
pub(crate) mod camera;
|
||||
pub(crate) mod client;
|
||||
pub(crate) mod sighting;
|
||||
pub(crate) mod user;
|
||||
|
@@ -1,20 +1,24 @@
|
||||
use crate::Backend;
|
||||
use crate::{
|
||||
model::{camera::Camera, client::Client},
|
||||
Backend,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{types::chrono::NaiveDateTime, FromRow};
|
||||
|
||||
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||
pub struct Sighting {
|
||||
pub user_uuid: String,
|
||||
pub client_uuid: String,
|
||||
pub sighted_at: NaiveDateTime,
|
||||
pub camera_id: String, // Changed from i64 to String to match TEXT/UUID in schema
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub(crate) async fn sightings_for_user_uuid(&self, uuid: &str) -> Vec<Sighting> {
|
||||
pub(crate) async fn sightings_for_client(&self, client: &Client) -> Vec<Sighting> {
|
||||
let uuid = client.uuid.to_string();
|
||||
match self {
|
||||
Backend::Sqlite(db) => sqlx::query_as!(
|
||||
Sighting,
|
||||
"SELECT user_uuid, sighted_at, camera_id FROM sightings WHERE user_uuid = ?",
|
||||
"SELECT client_uuid, sighted_at, camera_id FROM sightings WHERE client_uuid = ?",
|
||||
uuid
|
||||
)
|
||||
.fetch_all(db)
|
||||
@@ -22,4 +26,21 @@ impl Backend {
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn client_found_camera(&self, client: &Client, camera: &Camera) -> bool {
|
||||
let client_uuid = client.uuid.to_string();
|
||||
let camera_uuid = camera.uuid.to_string();
|
||||
|
||||
match self {
|
||||
Backend::Sqlite(db) => sqlx::query!(
|
||||
"INSERT INTO sightings(client_uuid, camera_id) VALUES (?, ?) RETURNING client_uuid",
|
||||
client_uuid,
|
||||
camera_uuid
|
||||
)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.unwrap(),
|
||||
};
|
||||
true
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user