hoppinworld_backend

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | LICENSE

commit e1b0523f390d00b4b2b585c2df5ab7b64aa57d21
parent cb815bc816d0da9f7c0777f32451987e3b9e58af
Author: Joël Lupien (Jojolepro) <jojolepromain@gmail.com>
Date:   Tue, 30 Oct 2018 20:35:52 -0400

Fixed duplicated user scores

Diffstat:
MCargo.lock | 48++++++++++++++++++++++++++++++++++++++++++++++++
MCargo.toml | 2+-
Msrc/db.rs | 23+++++++++++++++++++++++
Msrc/endpoint.rs | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 32++++----------------------------
Msrc/model.rs | 15++++++++++++++-
6 files changed, 170 insertions(+), 30 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -478,6 +478,16 @@ dependencies = [ ] [[package]] +name = "hyper-sync-rustls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki-roots 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "idna" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -908,6 +918,7 @@ dependencies = [ "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "cookie 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-sync-rustls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -915,6 +926,7 @@ dependencies = [ "ordermap 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "pear 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)", "pear_codegen 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "state 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", @@ -982,6 +994,19 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "rustls" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "ryu" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1297,6 +1322,25 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "webpki" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ring 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "webpki-roots" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "untrusted 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "webpki 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "winapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1381,6 +1425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" "checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" "checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2" +"checksum hyper-sync-rustls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6df6f419a9f116cc93b5f39a5ded1161e088a2c8424c8fcd1d4049193424a4" "checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" "checksum interpolate_idents 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "001fa82e9e01b73b7c11450434d0959dcbd46c0debc75c1841280cf3fbc68255" "checksum isatty 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e31a8281fc93ec9693494da65fbf28c0c2aa60a2eaec25dc58e2f31952e95edc" @@ -1438,6 +1483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rocket_cors 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af8ef9a2b1153d0ec537cde8a98e306ef838804cc57074738f0675b6571b0f7e" "checksum rocket_cors 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6893d8395e5d428e62a88c4fa26cc23153e95e1dbc6068cf4e97fafcef978eee" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum rustls 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17727f4b991294da2c84d75a43c003151ff58072212768800f66c56ee46dca43" "checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum safemem 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dca453248a96cb0749e36ccdfe2b0b4e54a61bfef89fb97ec621eb8e0a93dd9" @@ -1480,6 +1526,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "def296d3eb3b12371b2c7d0e83bfe1403e4db2d7a0bba324a12b21c4ee13143d" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum webpki 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e499345fc4c6b7c79a5b8756d4592c4305510a13512e79efafe00dfbd67bbac6" +"checksum webpki-roots 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5bfb3f50499f21ad2317f442845e3b5805b007f1e728f59885c99e61b8c181a7" "checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Joël Lupien (Jojolepro) <jojolepromain@gmail.com>"] edition = "2018" [dependencies] -rocket = "0.3" +rocket = { version = "0.3", features = ["tls"] } rocket_codegen = "0.3" diesel = { version = "1.3.3", features = ["mysql","r2d2","chrono"] } dotenv = "0.11.0" diff --git a/src/db.rs b/src/db.rs @@ -20,6 +20,13 @@ pub fn user_from_email(db: &DbConn, email: &String) -> DieselResult<User> { .first::<User>(&**db) } +/// Returns the user associated with the corresponding user id. +pub fn user_from_id(db: &DbConn, id: i32) -> DieselResult<User> { + user::table + .filter(user::id.eq(id)) + .first::<User>(&**db) +} + pub fn set_user_token(db: &DbConn, user_id: i32, token: &String) -> DieselResult<usize> { diesel::update( user::table.filter(user::columns::id.eq(user_id)), @@ -67,6 +74,10 @@ pub fn map_from_id(db: &DbConn, id: i32) -> DieselResult<Map> { .first::<Map>(&**db) } +pub fn map_list(db: &DbConn) -> DieselResult<Vec<Map>> { + map::table.load::<Map>(&**db) +} + pub fn score_from_user_map(db: &DbConn, userid: i32, mapid: i32, season: i32) -> DieselResult<Score> { score::table .filter(score::userid.eq(userid)) @@ -75,7 +86,19 @@ pub fn score_from_user_map(db: &DbConn, userid: i32, mapid: i32, season: i32) -> .first::<Score>(&**db) } +/// Returns the 25 best scores of a map for a specified season. +pub fn score_top_from_map(db: &DbConn, mapid: i32, season: i32) -> DieselResult<Vec<Score>> { + score::table + .filter(score::mapid.eq(mapid)) + .filter(score::season.eq(season)) + .order_by(score::total_time) + .limit(25) + .load::<Score>(&**db) +} + pub fn score_insert_or_replace(db: &DbConn, score: ScoreInsert) -> DieselResult<usize> { + diesel::delete(score::table.filter(score::userid.eq(score.userid))) + .execute(&**db)?; diesel::replace_into(score::table) .values(score) .execute(&**db) diff --git a/src/endpoint.rs b/src/endpoint.rs @@ -28,6 +28,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for UserLogged { if let Some(token) = token { let token = token.trim(); if !token.starts_with("Bearer ") { + info!("User failed token login with token: {}", token); return Outcome::Failure((Status::BadRequest, ())); } let token = &token[7..]; // Possible DOS, mitigated by trim() @@ -36,9 +37,11 @@ impl<'a, 'r> FromRequest<'a, 'r> for UserLogged { if let Ok(u) = user { Outcome::Success(UserLogged { user: u}) } else { + info!("User attempted to login with invalid bearer token: {}", token); Outcome::Failure((Status::Unauthorized, ())) } } else { + info!("User attempted to use secure route without Bearer header."); Outcome::Failure((Status::Unauthorized, ())) } } @@ -284,6 +287,7 @@ fn submit_score(user: UserLogged, db: DbConn, data: Json<ScoreInsertRequest>) -> } }, Err(diesel::result::Error::NotFound) => { + info!("User submitted score for invalid map id: {}", data.mapid); Err(ReturnStatus::new(Status::BadRequest).with_message("Invalid map id".to_string())) } Err(e) => { @@ -293,4 +297,80 @@ fn submit_score(user: UserLogged, db: DbConn, data: Json<ScoreInsertRequest>) -> } } +/// Returns top 25 scores on a map +#[get("/map/<mapid>/scores")] +fn map_scores(db: DbConn, mapid: i32) -> Result<Json<Vec<ScoreDisplay>>, ReturnStatus> { + let season = 1; + match score_top_from_map(&db, mapid, season) { + Ok(scores) => { + let score_displays = scores.iter().flat_map(|score| { + if let Ok(username) = user_from_id(&db, score.userid).map(|u| u.username){ + Some(ScoreDisplay { + userid: score.userid, + username: username, + segment_times: score.segment_times.clone(), + strafes: score.strafes, + jumps: score.jumps, + total_time: score.total_time, + }) + } else { + None + } + }).collect::<Vec<_>>(); + Ok(Json(score_displays)) + } + Err(e) => { + error!("Failed to query database: {}", e); + Err(ReturnStatus::new(Status::InternalServerError)) + } + } +} + +/// Returns the map info for a specific map +#[get("/map/<mapid>")] +fn map_info(db: DbConn, mapid: i32) -> Result<Json<Map>, ReturnStatus> { + match map_from_id(&db, mapid) { + Ok(map) => { + Ok(Json(map)) + } + Err(diesel::result::Error::NotFound) => { + Err(ReturnStatus::new(Status::BadRequest).with_message("Map not found for specified id".to_string())) + } + Err(e) => { + error!("Failed to query database: {}", e); + Err(ReturnStatus::new(Status::InternalServerError)) + } + } +} +/// Returns the top score of a user on the specified map. +#[get("/user/<userid>/scores/<mapid>")] +fn user_score_for_map(db: DbConn, userid: i32, mapid: i32) -> Result<Json<Score>, ReturnStatus> { + let season = 1; + match score_from_user_map(&db, userid, mapid, season) { + Ok(score) => { + Ok(Json(score)) + } + Err(diesel::result::Error::NotFound) => { + Err(ReturnStatus::new(Status::BadRequest).with_message("User has no score on this map".to_string())) + } + Err(e) => { + error!("Failed to query database: {}", e); + Err(ReturnStatus::new(Status::InternalServerError)) + } + } +} + +/// Returns the list of maps +#[get("/map")] +fn list_maps(db: DbConn) -> Result<Json<Vec<Map>>, ReturnStatus> { + match map_list(&db) { + Ok(maps) => { + Ok(Json(maps)) + } + Err(e) => { + error!("Failed to query database: {}", e); + Err(ReturnStatus::new(Status::InternalServerError)) + } + } +} diff --git a/src/main.rs b/src/main.rs @@ -21,31 +21,6 @@ extern crate backend_utils; extern crate lettre; extern crate lettre_email; - -use chrono::offset::Local; -use chrono::Duration; -use diesel::prelude::*; -use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; -use dotenv::dotenv; -use rocket::http::{Cookies, Status}; -use rocket::request::{self, Form, FromRequest}; -use rocket::response::content::Html; -use rocket::response::Redirect; -use rocket::Rocket; -use rocket::{Outcome, Request, State}; -use rocket_contrib::Json; -use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; -use std::env; -use std::fmt::{self, Display, Formatter}; -use std::ops::Deref; -use rocket::http::Method; -use rocket::response::status; -use rocket::response::status::BadRequest; -use rocket::response::Responder; -use rocket::Response; -use uuid::Uuid; - - pub use backend_utils::*; mod schema; @@ -58,9 +33,6 @@ pub use self::endpoint::*; - - - fn main() { let options = rocket_trajectory_restriction(None); rocket::ignite() @@ -72,6 +44,10 @@ fn main() { register, change_password, submit_score, + map_scores, + user_score_for_map, + list_maps, + map_info, ], ) .attach(options) diff --git a/src/model.rs b/src/model.rs @@ -60,7 +60,7 @@ pub struct LoginResult { pub token: String, } -#[derive(Queryable)] +#[derive(Queryable, Serialize)] pub struct Score { pub id: i32, pub userid: i32, @@ -75,9 +75,22 @@ pub struct Score { pub seasonid: i32, } +/// Score sent for the score listing of a map +#[derive(Serialize, Debug)] +pub struct ScoreDisplay { + pub userid: i32, + pub username: String, + pub segment_times: String, // 0.22344,2233.34,.. + pub strafes: i32, + pub jumps: i32, + /// Seconds + pub total_time: f32, +} + #[derive(Deserialize)] pub struct ScoreInsertRequest { pub mapid: i32, + /// Abs time when at segment end pub segment_times: Vec<f32>, pub strafes: i32, pub jumps: i32,