Start with the analysis stuff

This commit is contained in:
Lol3rrr
2024-09-12 00:13:26 +02:00
parent 1bfcd64a3c
commit f6500ce2cd
15 changed files with 498 additions and 23 deletions

231
Cargo.lock generated
View File

@@ -17,6 +17,19 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -156,6 +169,7 @@ dependencies = [
name = "backend"
version = "0.1.0"
dependencies = [
"ahash",
"async-trait",
"axum",
"common",
@@ -163,6 +177,8 @@ dependencies = [
"diesel-async",
"diesel_async_migrations",
"futures-util",
"memmap2",
"parser",
"reqwest 0.12.7",
"serde",
"serde_json",
@@ -202,6 +218,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit_reverse"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99528ca30abb9495c7e106bf7c3177b257c62040fc0f2909fe470b0f43097296"
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -214,6 +236,12 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bitter"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef3a13b71496a92e8c00ebe576b260655b56935cd5118d5a8949788b651b5e07"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -308,7 +336,7 @@ version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be"
dependencies = [
"convert_case",
"convert_case 0.6.0",
"lazy_static",
"nom",
"pathdiff",
@@ -336,6 +364,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "convert_case"
version = "0.6.0"
@@ -381,6 +415,31 @@ dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "crunchy"
version = "0.2.2"
@@ -397,6 +456,17 @@ dependencies = [
"typenum",
]
[[package]]
name = "csgoproto"
version = "0.1.5"
source = "git+https://github.com/LaihoE/demoparser.git#60974266a0374000f83d31172ac823135e93482b"
dependencies = [
"bytes",
"glob",
"proc-macro2",
"protobuf",
]
[[package]]
name = "darling"
version = "0.20.10"
@@ -466,6 +536,19 @@ dependencies = [
"syn",
]
[[package]]
name = "derive_more"
version = "0.99.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
dependencies = [
"convert_case 0.4.0",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "diesel"
version = "2.2.4"
@@ -771,6 +854,12 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "gloo-net"
version = "0.1.0"
@@ -1160,6 +1249,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
@@ -1227,7 +1325,7 @@ dependencies = [
"getrandom",
"html-escape",
"indexmap",
"itertools",
"itertools 0.12.1",
"js-sys",
"leptos_reactive",
"once_cell",
@@ -1270,9 +1368,9 @@ checksum = "90eaea005cabb879c091c84cfec604687ececfd540469e5a30a60c93489a2f23"
dependencies = [
"attribute-derive",
"cfg-if",
"convert_case",
"convert_case 0.6.0",
"html-escape",
"itertools",
"itertools 0.12.1",
"leptos_hot_reload",
"prettyplease",
"proc-macro-error",
@@ -1320,7 +1418,7 @@ checksum = "e5006e35b7c768905286dbea0d3525396cd39d961cb7b9fb664aa00b0c984ae6"
dependencies = [
"cfg-if",
"gloo-net 0.6.0",
"itertools",
"itertools 0.12.1",
"js-sys",
"lazy_static",
"leptos",
@@ -1453,6 +1551,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
dependencies = [
"libc",
]
[[package]]
name = "mime"
version = "0.3.17"
@@ -1660,6 +1767,33 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "parser"
version = "0.1.1"
source = "git+https://github.com/LaihoE/demoparser.git#60974266a0374000f83d31172ac823135e93482b"
dependencies = [
"ahash",
"bit_reverse",
"bitter",
"bytes",
"csgoproto",
"derive_more",
"itertools 0.13.0",
"lazy_static",
"libc",
"memmap2",
"phf",
"phf_macros",
"proc-macro2",
"protobuf",
"protobuf-support",
"rand",
"rayon",
"regex",
"serde",
"snap",
]
[[package]]
name = "paste"
version = "1.0.15"
@@ -1687,6 +1821,29 @@ dependencies = [
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.11.2"
@@ -1855,6 +2012,27 @@ dependencies = [
"yansi",
]
[[package]]
name = "protobuf"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df67496db1a89596beaced1579212e9b7c53c22dca1d9745de00ead76573d514"
dependencies = [
"bytes",
"once_cell",
"protobuf-support",
"thiserror",
]
[[package]]
name = "protobuf-support"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e2d30ab1878b2e72d1e2fc23ff5517799c9929e2cf81a8516f9f4dcf2b9cf3"
dependencies = [
"thiserror",
]
[[package]]
name = "quote"
version = "1.0.37"
@@ -1916,6 +2094,26 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
@@ -2087,6 +2285,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.36"
@@ -2224,6 +2431,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "send_wrapper"
version = "0.6.0"
@@ -2374,7 +2587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf0e6f71fc924df36e87f27dfbd447f0bedd092d365db3a5396878256d9f00c"
dependencies = [
"const_format",
"convert_case",
"convert_case 0.6.0",
"proc-macro2",
"quote",
"syn",
@@ -2448,6 +2661,12 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "snap"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
[[package]]
name = "socket2"
version = "0.5.7"

View File

@@ -24,3 +24,7 @@ diesel_async_migrations = { version = "0.15" }
reqwest = { version = "0.12", features = ["json"] }
common = { path = "../common/" }
l_demoparser = { package = "parser", git = "https://github.com/LaihoE/demoparser.git", ref = "main" }
ahash = { version = "0.8" }
memmap2 = { version = "0.9" }

131
backend/src/analysis.rs Normal file
View File

@@ -0,0 +1,131 @@
use std::path::PathBuf;
#[derive(Debug)]
pub struct AnalysisInput {
pub steamid: String,
pub demoid: i64,
pub path: PathBuf,
}
#[derive(Debug)]
pub struct BaseInfo {
pub map: String,
}
#[tracing::instrument(skip(input))]
pub fn analyse_base(input: AnalysisInput) -> BaseInfo {
tracing::info!("Performing Base analysis");
let huf = l_demoparser::second_pass::parser_settings::create_huffman_lookup_table();
let settings = l_demoparser::first_pass::parser_settings::ParserInputs {
wanted_players: Vec::new(),
real_name_to_og_name: ahash::AHashMap::default(),
wanted_player_props: vec!["X".to_string(), "team_num".to_string()],
wanted_events: vec!["player_death".to_string(), "player_team".to_string(), "team_info".to_string(), "player_spawn".to_string(), "team_score".to_string(), "round_end".to_string(), "game_end".to_string(), "match_end_conditions".to_string(), "switch_team".to_string(), "player_given_c4".to_string()],
wanted_other_props: vec![],
parse_ents: true,
wanted_ticks: Vec::new(),
parse_projectiles: false,
only_header: false,
count_props: false,
only_convars: false,
huffman_lookup_table: &huf,
order_by_steamid: false,
};
let mut ds = l_demoparser::parse_demo::Parser::new(settings, l_demoparser::parse_demo::ParsingMode::ForceSingleThreaded);
let file = std::fs::File::open(&input.path).unwrap();
let mmap = unsafe { memmap2::MmapOptions::new().map(&file).unwrap() };
let output = ds.parse_demo(&mmap).unwrap();
let header = output.header.as_ref().unwrap();
tracing::info!("Header: {:?}", header);
for event in output.game_events.iter() {
match event.name.as_str() {
"player_team" => {
let team = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
("team", Some(d)) => Some(d),
("team", None) => {
tracing::warn!("'team' field without data");
None
}
_ => None,
});
let user_name = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
("user_name", Some(d)) => Some(d),
("user_name", None) => {
tracing::warn!("'user_name' field without data");
None
}
_ => None,
});
let steamid = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
("user_steamid", Some(d)) => Some(d),
("user_steamid", None) => {
tracing::warn!("'user_steamid' field without data");
None
}
_ => None,
});
tracing::info!("'{:?}' ({:?}) -> {:?}", user_name, steamid, team);
}
"team_info" => {
tracing::info!("Team Info: {:?}", event);
}
"player_spawn" => {
// tracing::info!("Player Spawn: {:?}", event);
}
"team_score" => {
tracing::info!("Team Score: {:?}", event);
}
"round_end" => {
let winner = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
("winner", Some(d)) => Some(d),
("winner", None) => {
tracing::warn!("'winner' field without data");
None
}
_ => None,
});
let round = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
("round", Some(d)) => Some(d),
("round", None) => {
tracing::warn!("'round' field without data");
None
}
_ => None,
});
let reason = event.fields.iter().find_map(|f| match (f.name.as_str(), &f.data) {
("reason", Some(d)) => Some(d),
("reason", None) => {
tracing::warn!("'reason' field without data");
None
}
_ => None,
});
tracing::info!(?winner, ?round, ?reason, "Round End");
}
"game_end" => {
tracing::info!("Game End: {:?}", event);
}
"match_end_conditions" => {
tracing::info!("Match End Conditions: {:?}", event);
}
"switch_team" => {
tracing::info!("Switch Team: {:?}", event);
}
"player_given_c4" => {
tracing::info!("Player Given C4: {:?}", event);
}
_ => {}
};
}
BaseInfo {
map: header.get("map_name").cloned().unwrap_or_default()
}
}

View File

@@ -7,15 +7,17 @@ pub mod demos {
struct DemoState {
upload_folder: std::path::PathBuf,
base_analysis: tokio::sync::mpsc::UnboundedSender<crate::analysis::AnalysisInput>
}
pub fn router<P>(upload_folder: P) -> axum::Router where P: Into<std::path::PathBuf> {
pub fn router<P>(upload_folder: P, base_analysis: tokio::sync::mpsc::UnboundedSender<crate::analysis::AnalysisInput>) -> axum::Router where P: Into<std::path::PathBuf> {
axum::Router::new()
.route("/list", axum::routing::get(list))
.route("/upload", axum::routing::post(upload).layer(axum::extract::DefaultBodyLimit::max(500*1024*1024)))
.route("/:id/info", axum::routing::get(info))
.with_state(Arc::new(DemoState {
upload_folder: upload_folder.into(),
base_analysis,
}))
}
@@ -24,11 +26,12 @@ pub mod demos {
let steam_id = session.data().steam_id.ok_or_else(|| axum::http::StatusCode::UNAUTHORIZED)?;
tracing::info!("SteamID: {:?}", steam_id);
let query = crate::schema::demos::dsl::demos.filter(crate::schema::demos::dsl::steam_id.eq(steam_id.to_string()));
let results: Vec<crate::models::Demo> = query.load(&mut crate::db_connection().await).await.unwrap();
let query = crate::schema::demos::dsl::demos.inner_join(crate::schema::demo_info::dsl::demo_info).select((crate::models::Demo::as_select(), crate::models::DemoInfo::as_select())).filter(crate::schema::demos::dsl::steam_id.eq(steam_id.to_string()));
let results: Vec<(crate::models::Demo, crate::models::DemoInfo)> = query.load(&mut crate::db_connection().await).await.unwrap();
Ok(axum::response::Json(results.into_iter().map(|demo| common::BaseDemoInfo {
Ok(axum::response::Json(results.into_iter().map(|(demo, info)| common::BaseDemoInfo {
id: demo.demo_id,
map: info.map,
}).collect::<Vec<_>>()))
}
@@ -46,15 +49,29 @@ pub mod demos {
}
let timestamp_secs = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs();
let demo_id = timestamp_secs as i64;
let demo_file_path = user_folder.join(format!("{}.dem", timestamp_secs));
tokio::fs::write(demo_file_path, file_content).await.unwrap();
tokio::fs::write(&demo_file_path, file_content).await.unwrap();
let mut db_con = crate::db_connection().await;
let query = diesel::dsl::insert_into(crate::schema::demos::dsl::demos).values(crate::models::Demo {
demo_id: timestamp_secs as i64,
demo_id,
steam_id: steam_id.to_string(),
});
query.execute(&mut crate::db_connection().await).await.unwrap();
query.execute(&mut db_con).await.unwrap();
state.base_analysis.send(crate::analysis::AnalysisInput {
steamid: steam_id.to_string(),
demoid: demo_id,
path: demo_file_path,
});
let processing_query = diesel::dsl::insert_into(crate::schema::processing_status::dsl::processing_status).values(crate::models::ProcessingStatus {
demo_id,
info: 0,
});
processing_query.execute(&mut db_con).await.unwrap();
Ok(axum::response::Redirect::to("/"))
}
@@ -189,9 +206,9 @@ pub mod user {
}
}
pub fn router() -> axum::Router {
pub fn router(base_analysis: tokio::sync::mpsc::UnboundedSender<crate::analysis::AnalysisInput>) -> axum::Router {
axum::Router::new()
.nest("/steam/", steam::router("http://localhost:3000", "/api/steam/callback"))
.nest("/demos/", demos::router("uploads/"))
.nest("/demos/", demos::router("uploads/", base_analysis))
.nest("/user/", user::router())
}

View File

@@ -6,6 +6,8 @@ pub use usersession::{UserSessionData, UserSession};
pub mod diesel_sessionstore;
pub mod analysis;
pub async fn db_connection() -> diesel_async::AsyncPgConnection {
use diesel_async::AsyncConnection;

View File

@@ -26,6 +26,34 @@ async fn main() {
run_migrations(&mut backend::db_connection().await).await;
tracing::info!("Completed Migrations");
let (base_analysis_tx, mut base_analysis_rx) = tokio::sync::mpsc::unbounded_channel::<backend::analysis::AnalysisInput>();
tokio::task::spawn_blocking(move || {
while let Some(input) = base_analysis_rx.blocking_recv() {
let demo_id = input.demoid;
let result = backend::analysis::analyse_base(input);
dbg!(&result);
let handle = tokio::task::spawn(
async move {
let mut db_con = backend::db_connection().await;
let store_info_query = diesel::dsl::insert_into(backend::schema::demo_info::dsl::demo_info).values(backend::models::DemoInfo {
demo_id,
map: result.map,
});
let update_process_info = diesel::dsl::update(backend::schema::processing_status::dsl::processing_status).set(backend::schema::processing_status::dsl::info.eq(1)).filter(backend::schema::processing_status::dsl::demo_id.eq(demo_id));
tracing::trace!(?store_info_query, "Store demo info query");
tracing::trace!(?update_process_info, "Update processing info query");
store_info_query.execute(&mut db_con).await.unwrap();
update_process_info.execute(&mut db_con).await.unwrap();
}
);
}
});
let session_store = backend::diesel_sessionstore::DieselStore::new();
let session_layer = tower_sessions::SessionManagerLayer::new(session_store)
.with_secure(false)
@@ -38,7 +66,7 @@ async fn main() {
}
let router = axum::Router::new()
.nest("/api/", backend::api::router())
.nest("/api/", backend::api::router(base_analysis_tx))
.layer(session_layer)
.nest_service("/", tower_http::services::ServeDir::new("frontend/dist/"));

View File

@@ -24,3 +24,19 @@ pub struct User {
pub steamid: String,
pub name: String,
}
#[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::demo_info)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct DemoInfo {
pub demo_id: i64,
pub map: String,
}
#[derive(Queryable, Selectable, Insertable, Debug)]
#[diesel(table_name = crate::schema::processing_status)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct ProcessingStatus {
pub demo_id: i64,
pub info: i16,
}

View File

@@ -1,12 +1,26 @@
// @generated automatically by Diesel CLI.
diesel::table! {
demos (steam_id, demo_id) {
demo_info (demo_id) {
demo_id -> Int8,
map -> Text,
}
}
diesel::table! {
demos (demo_id) {
steam_id -> Text,
demo_id -> Int8,
}
}
diesel::table! {
processing_status (demo_id) {
demo_id -> Int8,
info -> Int2,
}
}
diesel::table! {
sessions (id) {
id -> Text,
@@ -22,8 +36,13 @@ diesel::table! {
}
}
diesel::joinable!(demo_info -> demos (demo_id));
diesel::joinable!(processing_status -> demos (demo_id));
diesel::allow_tables_to_appear_in_same_query!(
demo_info,
demos,
processing_status,
sessions,
users,
);

View File

@@ -1,6 +1,7 @@
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct BaseDemoInfo {
pub id: i64,
pub map: String,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]

View File

@@ -1,4 +1,5 @@
use leptos::*;
use leptos_router::{Route, Routes, Outlet, A};
#[leptos::component]
pub fn demo() -> impl leptos::IntoView {
@@ -12,6 +13,28 @@ pub fn demo() -> impl leptos::IntoView {
});
view! {
<h2>Demo - {id}</h2>
<h2>Demo - { id }</h2>
<div>
<A href="">Scoreboard</A>
<A href="perround">Per Round</A>
</div>
<div>
<Outlet/>
</div>
}
}
#[leptos::component]
pub fn scoreboard() -> impl leptos::IntoView {
view! {
<h3>Scoreboard</h3>
}
}
#[leptos::component]
pub fn per_round() -> impl leptos::IntoView {
view! {
<h3>Per Round</h3>
}
}

View File

@@ -1,7 +1,7 @@
use leptos::*;
use leptos_router::A;
mod demo;
pub mod demo;
pub use demo::Demo;
mod navbar;
@@ -17,7 +17,7 @@ pub enum DemoUploadStatus {
pub fn demo_list_entry(demo: common::BaseDemoInfo) -> impl leptos::IntoView {
view! {
<li>
<A href=format!("/demo/{}", demo.id)>Demo: {demo.id}</A>
<A href=format!("demo/{}", demo.id)>Demo: {demo.map} - {demo.id}</A>
</li>
}
}

View File

@@ -27,7 +27,10 @@ fn main() {
<Routes>
<Route path="/" view=Homepage />
<Route path="/demo/:id" view=Demo />
<Route path="/demo/:id" view=Demo>
<Route path="perround" view=frontend::demo::PerRound />
<Route path="" view=frontend::demo::Scoreboard />
</Route>
</Routes>
</main>
</Router>

View File

@@ -1,6 +1,5 @@
-- Your SQL goes here
CREATE TABLE IF NOT EXISTS demos (
steam_id TEXT NOT NULL,
demo_id bigint NOT NULL,
PRIMARY KEY(steam_id, demo_id)
demo_id bigint NOT NULL PRIMARY KEY
)

View File

@@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
DROP TABLE processing_status;
DROP TABLE demo_info

View File

@@ -0,0 +1,10 @@
-- Your SQL goes here
CREATE TABLE IF NOT EXISTS processing_status (
demo_id bigint PRIMARY KEY REFERENCES demos(demo_id),
info int2 NOT NULL -- the processing_status of the basic demo info
);
CREATE TABLE IF NOT EXISTS demo_info (
demo_id bigint PRIMARY KEY REFERENCES demos(demo_id),
map TEXT NOT NULL
)