only show top 10 participants + total amount

This commit is contained in:
2025-08-13 16:14:07 +02:00
parent 4ea9068850
commit 6d021e8d6b
4 changed files with 90 additions and 17 deletions

View File

@@ -29,6 +29,7 @@ location_linz: "Wo: überall in Linz"
game_title: "Wer findet die meisten Kameras?" game_title: "Wer findet die meisten Kameras?"
game_explanation_todo: "Willkommen zu unserem Überwachungsbewusstseinsspiel! Als Teil unserer Digital Shadows Ausstellung beim Ars Electronica Festival haben wir QR-Codes bei Überwachungskameras in ganz Linz platziert. Deine Mission: Entdecke die Kameras, scanne unsere Codes und finde heraus, wie allgegenwärtig öffentliche Überwachung wirklich ist. Wir sind aber nur Menschen wir haben nur einen kleinen Teil aller Kameras erfasst, die unsere Stadt beobachten. Wer beobachtet wen in unseren öffentlichen Räumen? Die Jagd beginnt jetzt! 🕵️" game_explanation_todo: "Willkommen zu unserem Überwachungsbewusstseinsspiel! Als Teil unserer Digital Shadows Ausstellung beim Ars Electronica Festival haben wir QR-Codes bei Überwachungskameras in ganz Linz platziert. Deine Mission: Entdecke die Kameras, scanne unsere Codes und finde heraus, wie allgegenwärtig öffentliche Überwachung wirklich ist. Wir sind aber nur Menschen wir haben nur einen kleinen Teil aller Kameras erfasst, die unsere Stadt beobachten. Wer beobachtet wen in unseren öffentlichen Räumen? Die Jagd beginnt jetzt! 🕵️"
save_button: "Speichern" save_button: "Speichern"
amount_participants: "In total there are %{amount} participants so far."
cameras_found: "Du hast %{found}/%{total} Kameras gefunden:" cameras_found: "Du hast %{found}/%{total} Kameras gefunden:"
highscore_title: "Bestenliste" highscore_title: "Bestenliste"
not_found_title: "ups" not_found_title: "ups"

View File

@@ -29,6 +29,7 @@ location_linz: "Where: all over Linz"
game_title: "Who finds the most cameras?" game_title: "Who finds the most cameras?"
game_explanation_todo: "Welcome to our public surveillance awareness game! As part of our Digital Shadows exhibition at Ars Electronica Festival, we've placed QR codes near surveillance cameras throughout Linz. Your mission: spot the cameras, scan our codes, and discover how pervasive public monitoring really is. We're only human though we've mapped just a small subset of all the cameras watching our city. Who's watching whom in our public spaces? The hunt begins now! 🕵️" game_explanation_todo: "Welcome to our public surveillance awareness game! As part of our Digital Shadows exhibition at Ars Electronica Festival, we've placed QR codes near surveillance cameras throughout Linz. Your mission: spot the cameras, scan our codes, and discover how pervasive public monitoring really is. We're only human though we've mapped just a small subset of all the cameras watching our city. Who's watching whom in our public spaces? The hunt begins now! 🕵️"
save_button: "Save" save_button: "Save"
amount_participants: "Aktuell gibt es insgesamt %{amount} Teilnehmer."
cameras_found: "You have found %{found}/%{total} cameras:" cameras_found: "You have found %{found}/%{total} cameras:"
highscore_title: "Highscore" highscore_title: "Highscore"
not_found_title: "uups" not_found_title: "uups"

View File

@@ -1,17 +1,17 @@
use crate::{ use crate::{
AppState, Backend, NameUpdateError,
language::language, language::language,
page::{MyMessage, Page}, page::{MyMessage, Page},
AppState, Backend, NameUpdateError,
}; };
use axum::{ use axum::{
Form, Router,
extract::{Path, State}, extract::{Path, State},
http::HeaderMap, http::HeaderMap,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::{get, post}, routing::{get, post},
Form, Router,
}; };
use axum_extra::extract::{CookieJar, PrivateCookieJar}; use axum_extra::extract::{CookieJar, PrivateCookieJar};
use maud::{Markup, PreEscaped, html}; use maud::{html, Markup, PreEscaped};
use serde::Deserialize; use serde::Deserialize;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
@@ -38,7 +38,8 @@ async fn retu(
let sightings = backend.sightings_for_client(&client).await; let sightings = backend.sightings_for_client(&client).await;
let amount_total_cameras = backend.amount_total_cameras().await; let amount_total_cameras = backend.amount_total_cameras().await;
let highscore = backend.highscore().await; let highscore = backend.highscore(&client).await;
let amount_participants = backend.amount_participants().await;
let mut page = Page::new(req.lang); let mut page = Page::new(req.lang);
if let Some(message) = message { if let Some(message) = message {
@@ -85,6 +86,13 @@ async fn retu(
h2 { (t!("highscore_title")) } h2 { (t!("highscore_title")) }
ul.iterated { ul.iterated {
@for rank in highscore { @for rank in highscore {
@if rank.show_dots_above {
li.card {
span {
""
}
}
}
li.card { li.card {
span { span {
span.font-headline.rank.text-muted { (rank.rank) "." } span.font-headline.rank.text-muted { (rank.rank) "." }
@@ -100,6 +108,7 @@ async fn retu(
} }
} }
} }
(t!("amount_participants", amount = amount_participants))
} }
}); });

View File

@@ -1,17 +1,32 @@
use crate::{Backend, model::client::Client}; use crate::{model::client::Client, Backend};
pub(crate) struct Rank { pub(crate) struct Rank {
pub(crate) rank: i64, pub(crate) rank: i64,
pub(crate) client: Client, pub(crate) client: Client,
pub(crate) amount: i64, pub(crate) amount: i64,
pub(crate) show_dots_above: bool,
} }
impl Backend { impl Backend {
pub(crate) async fn highscore(&self) -> Vec<Rank> { pub(crate) async fn amount_participants(&self) -> i64 {
match self {
Backend::Sqlite(db) => {
let row = sqlx::query!("SELECT COUNT(*) as count FROM client")
.fetch_one(db)
.await
.unwrap();
row.count
}
}
}
pub(crate) async fn highscore(&self, client: &Client) -> Vec<Rank> {
match self { match self {
Backend::Sqlite(db) => { Backend::Sqlite(db) => {
let rows = sqlx::query!( let rows = sqlx::query!(
"SELECT "WITH ranked_clients AS (
SELECT
DENSE_RANK() OVER (ORDER BY COUNT(s.client_uuid) DESC) as rank, DENSE_RANK() OVER (ORDER BY COUNT(s.client_uuid) DESC) as rank,
c.name, c.name,
c.uuid, c.uuid,
@@ -19,22 +34,69 @@ impl Backend {
FROM client c FROM client c
LEFT JOIN sightings s ON c.uuid = s.client_uuid LEFT JOIN sightings s ON c.uuid = s.client_uuid
GROUP BY c.uuid, c.name GROUP BY c.uuid, c.name
ORDER BY amount DESC" )
SELECT rank, name, uuid, amount
FROM ranked_clients
WHERE rank <= (
SELECT rank
FROM ranked_clients
ORDER BY rank
LIMIT 1 OFFSET 9
)
ORDER BY rank, name"
) )
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap_or_default(); .unwrap_or_default();
rows.into_iter() let mut ret: Vec<Rank> = rows
.into_iter()
.map(|row| Rank { .map(|row| Rank {
rank: row.rank.unwrap(),
client: Client {
uuid: row.uuid.unwrap(),
name: row.name,
},
amount: row.amount.unwrap(),
show_dots_above: false,
})
.collect();
let user_is_in_top = ret.iter().find(|x| &x.client == client).is_some();
if !user_is_in_top {
let row = sqlx::query!(
"WITH ranked_clients AS (
SELECT
DENSE_RANK() OVER (ORDER BY COUNT(s.client_uuid) DESC) as rank,
c.name,
c.uuid,
COUNT(s.client_uuid) as amount
FROM client c
LEFT JOIN sightings s ON c.uuid = s.client_uuid
GROUP BY c.uuid, c.name
)
SELECT rank, name, uuid, amount
FROM ranked_clients
WHERE uuid = ?
ORDER BY rank, name",
client.uuid
)
.fetch_one(db)
.await
.unwrap();
ret.push(Rank {
rank: row.rank, rank: row.rank,
client: Client { client: Client {
uuid: row.uuid, uuid: row.uuid,
name: row.name, name: row.name,
}, },
amount: row.amount, amount: row.amount,
show_dots_above: true,
}) })
.collect() }
ret
} }
} }
} }