prepare for private demos

This commit is contained in:
Robin Appelman 2025-04-02 20:37:03 +02:00
commit ffb230045c
6 changed files with 58 additions and 14 deletions

View file

@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT\n demos.id, demos.name, url, map, red, blu, uploader, duration, demos.created_at,\n \"scoreRed\" as score_red, \"scoreBlue\" as score_blue, server, nick,\n \"playerCount\" as player_count,\n users_named.name as uploader_name_preferred,\n users.steamid as \"uploader_steam_id?: SteamId\",\n users.name as \"uploader_name?\"\n FROM demos\n LEFT JOIN users_named ON uploader = users_named.id\n LEFT JOIN users ON uploader = users.id\n WHERE deleted_at IS NULL AND demos.id = $1",
"query": "SELECT\n demos.id, demos.name, url, map, red, blu, uploader, duration, demos.created_at,\n \"scoreRed\" as score_red, \"scoreBlue\" as score_blue, server, nick,\n \"playerCount\" as player_count,\n users_named.name as uploader_name_preferred,\n users.steamid as \"uploader_steam_id?: SteamId\",\n users.name as \"uploader_name?\",\n demos.private_until\n FROM demos\n LEFT JOIN users_named ON uploader = users_named.id\n LEFT JOIN users ON uploader = users.id\n WHERE deleted_at IS NULL AND demos.id = $1",
"describe": {
"columns": [
{
@ -87,6 +87,11 @@
"ordinal": 16,
"name": "uploader_name?",
"type_info": "Varchar"
},
{
"ordinal": 17,
"name": "private_until",
"type_info": "Timestamptz"
}
],
"parameters": {
@ -109,8 +114,9 @@
false,
true,
false,
false
false,
true
]
},
"hash": "68e8265e13596ad6de3e3d346b6d2fb62a68eda3e98f690e7e8d954627d8025c"
"hash": "7ea7c5f61e8c77898d8b816fce80f066026822e03d6f74bd4ae19d7c9d914426"
}

View file

@ -10,10 +10,7 @@
inputs.flakelight.follows = "flakelight";
};
};
outputs = {
mill-scale,
...
}:
outputs = {mill-scale, ...}:
mill-scale ./. {
packageOpts = {demostf-frontend-node-modules, ...}: {
preBuild = ''

View file

@ -40,6 +40,7 @@ pub struct Demo {
pub player_count: i32,
pub players: Vec<Player>,
pub chat: Vec<Chat>,
pub private_until: Option<OffsetDateTime>,
}
impl Debug for Demo {
@ -74,6 +75,7 @@ impl Demo {
pub server: String,
pub nick: String,
pub player_count: i32,
pub private_until: Option<OffsetDateTime>,
}
let Some(raw) = query_as!(
@ -84,7 +86,8 @@ impl Demo {
"playerCount" as player_count,
users_named.name as uploader_name_preferred,
users.steamid as "uploader_steam_id?: SteamId",
users.name as "uploader_name?"
users.name as "uploader_name?",
demos.private_until
FROM demos
LEFT JOIN users_named ON uploader = users_named.id
LEFT JOIN users ON uploader = users.id
@ -120,6 +123,7 @@ impl Demo {
player_count: raw.player_count,
players,
chat,
private_until: raw.private_until,
}))
}
@ -149,6 +153,37 @@ impl Demo {
pub fn viewer_url(&self) -> ViewerUrl {
ViewerUrl(self.id)
}
pub fn is_private(&self) -> bool {
if let Some(private_until) = self.private_until {
let now = OffsetDateTime::now_utc();
now < private_until
} else {
false
}
}
pub fn url(&self) -> &str {
if self.is_private() {
""
} else {
self.url.as_str()
}
}
pub fn private_until_text(&self) -> Cow<'static, str> {
if let Some(private_until) = self.private_until {
let now = OffsetDateTime::now_utc();
let days = (private_until - now).whole_days();
if days <= 1 {
"by tomorrow".into()
} else {
format!("in {days} days").into()
}
} else {
"".into()
}
}
}
pub struct ViewerUrl(i32);

View file

@ -23,7 +23,9 @@ impl Page for DemoPage {
fn render(&self) -> Markup {
let style_url = ClassIconsStyle::url();
html! {
@if self.demo.url.is_empty() {
@if self.demo.is_private() {
h3.warning { "This demo is private, it will be available for download " (self.demo.private_until_text()) }
} @else if self.demo.url.is_empty() {
h3.warning { "This demo has been deleted and is no longer available for download." }
}
h2 { (self.demo.server) " - " (self.demo.red) " vs " (self.demo.blu) }
@ -104,8 +106,8 @@ impl Page for DemoPage {
span.time { (self.demo.duration()) }
}
p.demo-download {
@if !self.demo.url.is_empty() {
a.button.button-primary href = (self.demo.url) download = (self.demo.name) rel = "nofollow" { "Download" }
@if !self.demo.url().is_empty() {
a.button.button-primary href = (self.demo.url()) download = (self.demo.name) rel = "nofollow" { "Download" }
a.button href = (self.demo.viewer_url()) rel = "nofollow" { "View" }
}
@if !self.demo.chat.is_empty() {

View file

@ -53,8 +53,12 @@ impl Page for ViewerPage<'_> {
html! {
.viewer-page data-maps = (maps) data-sync = (sync) {
@if let Some(demo) = self.demo.as_ref() {
@if demo.is_private() {
h3.warning { "This demo is private, it will be available for download " (demo.private_until_text()) }
} @else {
input type = "hidden" name = "url" value = (demo.url) {}
progress.download min = "0" max = "100" value = "0" {}
}
} @else {
.dropzone role = "button" {
noscript {