From 81ff11603f56a06f1ac5b2a35c8569a79e67aa1a Mon Sep 17 00:00:00 2001 From: "MAINMETALGEAR\\ededd" Date: Wed, 4 Dec 2024 17:18:33 -0500 Subject: [PATCH 1/4] Add in the leaderboard to the site! --- frontend/src/components/Leaderboard.tsx | 73 +++++++++++++++++++++++++ frontend/src/index.tsx | 2 +- src/controllers/players.rs | 12 +++- 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/Leaderboard.tsx diff --git a/frontend/src/components/Leaderboard.tsx b/frontend/src/components/Leaderboard.tsx new file mode 100644 index 0000000..df4ffbf --- /dev/null +++ b/frontend/src/components/Leaderboard.tsx @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; + +interface Player { + id: number; + player_name: string; + current_elo: number; + discord_id: string; + pug_wins: number; + pug_losses: number; + pug_draws: number; +} + +const Leaderboard: React.FC = () => { + const [players, setPlayers] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetch('/api/players/by-elo') + .then(response => response.json()) + .then(data => { + setPlayers(data); + setLoading(false); + }) + .catch(error => { + console.error('Error fetching leaderboard:', error); + setLoading(false); + }); + }, []); + + if (loading) return
Loading...
; + + return ( +
+

Leaderboard

+
+ + + + + + + + + + + + + {players.map((player, index) => ( + + + + + + + + + ))} + +
RankPlayerELOWLD
{index + 1} + + {player.player_name} + + {player.current_elo}{player.pug_wins}{player.pug_losses}{player.pug_draws}
+
+
+ ); +}; + +export default Leaderboard; \ No newline at end of file diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index aac5de2..bbb11d5 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -3,6 +3,7 @@ import ReactDOM from "react-dom/client"; import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'; import MatchesTable from "./components/MatchesTable"; import PlayerMatches from "./components/PlayerMatches"; +import Leaderboard from "./components/Leaderboard"; import "./index.css"; @@ -40,7 +41,6 @@ const Navigation: React.FC = () => { }; // Stub components for new routes -const Leaderboard: React.FC = () =>
Leaderboard Coming Soon!
; const About: React.FC = () =>
About Page Coming Soon!
; const App: React.FC = () => { diff --git a/src/controllers/players.rs b/src/controllers/players.rs index 1fd6f98..88b2b16 100644 --- a/src/controllers/players.rs +++ b/src/controllers/players.rs @@ -1,7 +1,7 @@ #![allow(clippy::unused_async)] use axum::debug_handler; use loco_rs::prelude::*; -use sea_orm::{DbBackend, EntityTrait, Statement}; +use sea_orm::{DbBackend, EntityTrait, QueryOrder, Statement}; use crate::models::_entities::players::{Entity, Column}; @@ -48,10 +48,20 @@ pub async fn get_by_name(Path(name): Path, State(ctx): State } } +#[debug_handler] +pub async fn list_by_elo(State(ctx): State) -> Result { + format::json(Entity::find() + .filter(Column::DeletedAt.is_null()) + .order_by_desc(Column::CurrentElo) + .all(&ctx.db) + .await?) +} + pub fn routes() -> Routes { Routes::new() .prefix("api/players") .add("/", get(list)) + .add("/by-elo", get(list_by_elo)) .add("/:id", get(get_one)) .add("/discord/:discord_id", get(get_by_discord_id)) .add("/name/:name", get(get_by_name)) From 934b51712f5d9ca841bcb0a03d647031b001ef3d Mon Sep 17 00:00:00 2001 From: "MAINMETALGEAR\\ededd" Date: Thu, 12 Dec 2024 16:33:35 -0500 Subject: [PATCH 2/4] Add winrate to leaderboard, update about tab --- frontend/src/components/About.tsx | 32 +++++++++++++++++++++++++ frontend/src/components/Leaderboard.tsx | 4 ++++ frontend/src/index.tsx | 4 +--- 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/About.tsx diff --git a/frontend/src/components/About.tsx b/frontend/src/components/About.tsx new file mode 100644 index 0000000..e81ec45 --- /dev/null +++ b/frontend/src/components/About.tsx @@ -0,0 +1,32 @@ +import React from "react"; + +const About: React.FC = () => ( +
+
+

TFPugs

+
+

+ TFpugs is a discord community for playing Team Fortress Classic pickup games, we mostly play 4v4 CTF games. +

+

+ I'm EDEdDNEdDYFaN on discord as well as everywhere else online. +

+ +
+
+
+); + +export default About; \ No newline at end of file diff --git a/frontend/src/components/Leaderboard.tsx b/frontend/src/components/Leaderboard.tsx index df4ffbf..ff4041b 100644 --- a/frontend/src/components/Leaderboard.tsx +++ b/frontend/src/components/Leaderboard.tsx @@ -43,6 +43,7 @@ const Leaderboard: React.FC = () => { W L D + Win % @@ -61,6 +62,9 @@ const Leaderboard: React.FC = () => { {player.pug_wins} {player.pug_losses} {player.pug_draws} + + {((player.pug_wins / (player.pug_wins + player.pug_losses)) * 100 || 0).toFixed(1)}% + ))} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index bbb11d5..291e4c2 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -4,6 +4,7 @@ import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'; import MatchesTable from "./components/MatchesTable"; import PlayerMatches from "./components/PlayerMatches"; import Leaderboard from "./components/Leaderboard"; +import About from "./components/About"; import "./index.css"; @@ -40,9 +41,6 @@ const Navigation: React.FC = () => { ); }; -// Stub components for new routes -const About: React.FC = () =>
About Page Coming Soon!
; - const App: React.FC = () => { return ( From b2a0a4090f18e4a8913b8bbe654776f7df1e1962 Mon Sep 17 00:00:00 2001 From: "MAINMETALGEAR\\ededd" Date: Fri, 13 Dec 2024 10:31:13 -0500 Subject: [PATCH 3/4] Updated the web-app to connect directly to read-only mysql db --- .devcontainer/.env | 2 +- Cargo.toml | 1 + src/app.rs | 6 +- src/bin/shuttle.rs | 4 +- src/controllers/auth.rs | 150 --------------- src/controllers/matches.rs | 6 +- src/controllers/mod.rs | 2 - src/controllers/player_elo.rs | 12 +- src/controllers/players.rs | 6 +- src/controllers/user.rs | 16 -- src/lib.rs | 4 +- src/mailers/auth.rs | 65 ------- src/mailers/auth/forgot/html.t | 11 -- src/mailers/auth/forgot/subject.t | 1 - src/mailers/auth/forgot/text.t | 3 - src/mailers/auth/welcome/html.t | 13 -- src/mailers/auth/welcome/subject.t | 1 - src/mailers/auth/welcome/text.t | 4 - src/mailers/mod.rs | 1 - src/models/_entities/matches.rs | 8 +- src/models/_entities/mod.rs | 2 - src/models/_entities/notes.rs | 4 +- src/models/_entities/player_elo.rs | 14 +- src/models/_entities/players.rs | 8 +- src/models/_entities/prelude.rs | 3 +- src/models/_entities/users.rs | 28 --- src/models/mod.rs | 1 - src/models/users.rs | 297 ----------------------------- src/views/auth.rs | 23 --- src/views/mod.rs | 2 - src/views/user.rs | 21 -- src/workers/downloader.rs | 10 +- 32 files changed, 40 insertions(+), 689 deletions(-) delete mode 100644 src/controllers/auth.rs delete mode 100644 src/controllers/user.rs delete mode 100644 src/mailers/auth.rs delete mode 100644 src/mailers/auth/forgot/html.t delete mode 100644 src/mailers/auth/forgot/subject.t delete mode 100644 src/mailers/auth/forgot/text.t delete mode 100644 src/mailers/auth/welcome/html.t delete mode 100644 src/mailers/auth/welcome/subject.t delete mode 100644 src/mailers/auth/welcome/text.t delete mode 100644 src/mailers/mod.rs delete mode 100644 src/models/_entities/users.rs delete mode 100644 src/models/users.rs delete mode 100644 src/views/auth.rs delete mode 100644 src/views/mod.rs delete mode 100644 src/views/user.rs diff --git a/.devcontainer/.env b/.devcontainer/.env index 1a3ca5b..a58979f 100644 --- a/.devcontainer/.env +++ b/.devcontainer/.env @@ -1,6 +1,6 @@ POSTGRES_DB=loco_app POSTGRES_USER=loco POSTGRES_PASSWORD=loco -DATABASE_URL=postgres://loco:loco@db:5432/loco_app +DATABASE_URL=mysql://readonly:readonly_user_breaking_points_fan_tfpugs_online@tfpugs-mysql.cw57ix0bewxi.us-east-1.rds.amazonaws.com:3306/tfpugs REDIS_URL=redis://redis:6379 MAILER_HOST=mailer \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 5718eb5..4be92aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ validator = { version = "0.16" } sea-orm = { version = "1.0.0", features = [ "sqlx-sqlite", "sqlx-postgres", + "sqlx-mysql", "runtime-tokio-rustls", "macros", ] } diff --git a/src/app.rs b/src/app.rs index 60305a1..aa6d7b0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -16,7 +16,7 @@ use sea_orm::DatabaseConnection; use crate::{ controllers, initializers, - models::_entities::{notes, users}, + models::_entities::notes, tasks, workers::downloader::DownloadWorker, }; @@ -54,8 +54,6 @@ impl Hooks for App { .add_route(controllers::players::routes()) .add_route(controllers::matches::routes()) .add_route(controllers::notes::routes()) - .add_route(controllers::auth::routes()) - .add_route(controllers::user::routes()) } fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext) { @@ -67,13 +65,11 @@ impl Hooks for App { } async fn truncate(db: &DatabaseConnection) -> Result<()> { - truncate_table(db, users::Entity).await?; truncate_table(db, notes::Entity).await?; Ok(()) } async fn seed(db: &DatabaseConnection, base: &Path) -> Result<()> { - db::seed::(db, &base.join("users.yaml").display().to_string()).await?; db::seed::(db, &base.join("notes.yaml").display().to_string()).await?; Ok(()) } diff --git a/src/bin/shuttle.rs b/src/bin/shuttle.rs index 9323a5b..bb9c541 100644 --- a/src/bin/shuttle.rs +++ b/src/bin/shuttle.rs @@ -6,10 +6,10 @@ use shuttle_runtime::DeploymentMetadata; #[shuttle_runtime::main] async fn main( - #[shuttle_shared_db::Postgres] conn_str: String, + #[shuttle_shared_db::Postgres] _conn_str: String, #[shuttle_runtime::Metadata] meta: DeploymentMetadata, ) -> shuttle_axum::ShuttleAxum { - std::env::set_var("DATABASE_URL", conn_str); + // std::env::set_var("DATABASE_URL", conn_str); let environment = match meta.env { shuttle_runtime::Environment::Local => Environment::Development, shuttle_runtime::Environment::Deployment => Environment::Production, diff --git a/src/controllers/auth.rs b/src/controllers/auth.rs deleted file mode 100644 index 9978d9d..0000000 --- a/src/controllers/auth.rs +++ /dev/null @@ -1,150 +0,0 @@ -use axum::debug_handler; -use loco_rs::prelude::*; -use serde::{Deserialize, Serialize}; - -use crate::{ - mailers::auth::AuthMailer, - models::{ - _entities::users, - users::{LoginParams, RegisterParams}, - }, - views::auth::LoginResponse, -}; -#[derive(Debug, Deserialize, Serialize)] -pub struct VerifyParams { - pub token: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ForgotParams { - pub email: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct ResetParams { - pub token: String, - pub password: String, -} - -/// Register function creates a new user with the given parameters and sends a -/// welcome email to the user -#[debug_handler] -async fn register( - State(ctx): State, - Json(params): Json, -) -> Result { - let res = users::Model::create_with_password(&ctx.db, ¶ms).await; - - let user = match res { - Ok(user) => user, - Err(err) => { - tracing::info!( - message = err.to_string(), - user_email = ¶ms.email, - "could not register user", - ); - return format::json(()); - } - }; - - let user = user - .into_active_model() - .set_email_verification_sent(&ctx.db) - .await?; - - AuthMailer::send_welcome(&ctx, &user).await?; - - format::json(()) -} - -/// Verify register user. if the user not verified his email, he can't login to -/// the system. -#[debug_handler] -async fn verify( - State(ctx): State, - Json(params): Json, -) -> Result { - let user = users::Model::find_by_verification_token(&ctx.db, ¶ms.token).await?; - - if user.email_verified_at.is_some() { - tracing::info!(pid = user.pid.to_string(), "user already verified"); - } else { - let active_model = user.into_active_model(); - let user = active_model.verified(&ctx.db).await?; - tracing::info!(pid = user.pid.to_string(), "user verified"); - } - - format::json(()) -} - -/// In case the user forgot his password this endpoints generate a forgot token -/// and send email to the user. In case the email not found in our DB, we are -/// returning a valid request for for security reasons (not exposing users DB -/// list). -#[debug_handler] -async fn forgot( - State(ctx): State, - Json(params): Json, -) -> Result { - let Ok(user) = users::Model::find_by_email(&ctx.db, ¶ms.email).await else { - // we don't want to expose our users email. if the email is invalid we still - // returning success to the caller - return format::json(()); - }; - - let user = user - .into_active_model() - .set_forgot_password_sent(&ctx.db) - .await?; - - AuthMailer::forgot_password(&ctx, &user).await?; - - format::json(()) -} - -/// reset user password by the given parameters -#[debug_handler] -async fn reset(State(ctx): State, Json(params): Json) -> Result { - let Ok(user) = users::Model::find_by_reset_token(&ctx.db, ¶ms.token).await else { - // we don't want to expose our users email. if the email is invalid we still - // returning success to the caller - tracing::info!("reset token not found"); - - return format::json(()); - }; - user.into_active_model() - .reset_password(&ctx.db, ¶ms.password) - .await?; - - format::json(()) -} - -/// Creates a user login and returns a token -#[debug_handler] -async fn login(State(ctx): State, Json(params): Json) -> Result { - let user = users::Model::find_by_email(&ctx.db, ¶ms.email).await?; - - let valid = user.verify_password(¶ms.password); - - if !valid { - return unauthorized("unauthorized!"); - } - - let jwt_secret = ctx.config.get_jwt_config()?; - - let token = user - .generate_jwt(&jwt_secret.secret, &jwt_secret.expiration) - .or_else(|_| unauthorized("unauthorized!"))?; - - format::json(LoginResponse::new(&user, &token)) -} - -pub fn routes() -> Routes { - Routes::new() - .prefix("api/auth") - .add("/register", post(register)) - .add("/verify", post(verify)) - .add("/login", post(login)) - .add("/forgot", post(forgot)) - .add("/reset", post(reset)) -} diff --git a/src/controllers/matches.rs b/src/controllers/matches.rs index f38df8c..fde12cc 100644 --- a/src/controllers/matches.rs +++ b/src/controllers/matches.rs @@ -13,7 +13,7 @@ pub async fn list(State(ctx): State) -> Result { } #[debug_handler] -pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { +pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { let match_item = Matches::find_by_id(id).one(&ctx.db).await?; match match_item { Some(m) => format::json(m), @@ -25,8 +25,8 @@ pub async fn get_one(Path(id): Path, State(ctx): State) -> Resu pub async fn get_matches_by_player_name(Path(player_name): Path, State(ctx): State) -> Result { // First, find the player's discord_id let statement = Statement::from_sql_and_values( - DbBackend::Postgres, - r#"SELECT * FROM players WHERE LOWER(player_name) = LOWER($1)"#, + DbBackend::MySql, + r#"SELECT * FROM players WHERE LOWER(player_name) = LOWER(?)"#, [player_name.clone().into()] ); let player = Players::find() diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index 70d0175..1aa875a 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -1,6 +1,4 @@ -pub mod auth; pub mod notes; -pub mod user; pub mod matches; pub mod players; pub mod player_elo; \ No newline at end of file diff --git a/src/controllers/player_elo.rs b/src/controllers/player_elo.rs index fd7ff52..bc7586d 100644 --- a/src/controllers/player_elo.rs +++ b/src/controllers/player_elo.rs @@ -22,15 +22,23 @@ pub async fn get_player_elo_by_player_name( Path(player_name): Path, State(ctx): State ) -> Result { + println!("Received player_name: {}", player_name); + let statement = Statement::from_sql_and_values( - DbBackend::Postgres, - r#"SELECT * FROM player_elo WHERE LOWER(player_name) = LOWER($1) ORDER BY created_at ASC"#, + DbBackend::MySql, + r#"SELECT * FROM player_elo WHERE LOWER(player_name) = LOWER(?) ORDER BY created_at ASC"#, [player_name.clone().into()] ); + + println!("SQL Query: {}, Parameters: {:?}", statement.sql, statement.values); + let player_elo = Entity::find() .from_raw_sql(statement) .all(&ctx.db) .await?; + + println!("Query result: {:?}", player_elo); + format::json(player_elo) } diff --git a/src/controllers/players.rs b/src/controllers/players.rs index 88b2b16..abcb917 100644 --- a/src/controllers/players.rs +++ b/src/controllers/players.rs @@ -11,7 +11,7 @@ pub async fn list(State(ctx): State) -> Result { } #[debug_handler] -pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { +pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { let player = Entity::find_by_id(id).one(&ctx.db).await?; match player { Some(p) => format::json(p), @@ -31,8 +31,8 @@ pub async fn get_by_discord_id(Path(discord_id): Path, State(ctx): State #[debug_handler] pub async fn get_by_name(Path(name): Path, State(ctx): State) -> Result { let statement = Statement::from_sql_and_values( - DbBackend::Postgres, - r#"SELECT * FROM players WHERE LOWER(player_name) = LOWER($1)"#, + DbBackend::MySql, + r#"SELECT * FROM players WHERE LOWER(player_name) = LOWER(?)"#, [name.clone().into()] ); diff --git a/src/controllers/user.rs b/src/controllers/user.rs deleted file mode 100644 index a7c0af3..0000000 --- a/src/controllers/user.rs +++ /dev/null @@ -1,16 +0,0 @@ -use axum::debug_handler; -use loco_rs::prelude::*; - -use crate::{models::_entities::users, views::user::CurrentResponse}; - -#[debug_handler] -async fn current(auth: auth::JWT, State(ctx): State) -> Result { - let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; - format::json(CurrentResponse::new(&user)) -} - -pub fn routes() -> Routes { - Routes::new() - .prefix("api/user") - .add("/current", get(current)) -} diff --git a/src/lib.rs b/src/lib.rs index dc3ea76..a15d676 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ pub mod app; pub mod controllers; pub mod initializers; -pub mod mailers; pub mod models; pub mod tasks; -pub mod views; -pub mod workers; +pub mod workers; \ No newline at end of file diff --git a/src/mailers/auth.rs b/src/mailers/auth.rs deleted file mode 100644 index 30bb1bf..0000000 --- a/src/mailers/auth.rs +++ /dev/null @@ -1,65 +0,0 @@ -// auth mailer -#![allow(non_upper_case_globals)] - -use loco_rs::prelude::*; -use serde_json::json; - -use crate::models::users; - -static welcome: Dir<'_> = include_dir!("src/mailers/auth/welcome"); -static forgot: Dir<'_> = include_dir!("src/mailers/auth/forgot"); -// #[derive(Mailer)] // -- disabled for faster build speed. it works. but lets -// move on for now. - -#[allow(clippy::module_name_repetitions)] -pub struct AuthMailer {} -impl Mailer for AuthMailer {} -impl AuthMailer { - /// Sending welcome email the the given user - /// - /// # Errors - /// - /// When email sending is failed - pub async fn send_welcome(ctx: &AppContext, user: &users::Model) -> Result<()> { - Self::mail_template( - ctx, - &welcome, - mailer::Args { - to: user.email.to_string(), - locals: json!({ - "name": user.name, - "verifyToken": user.email_verification_token, - "domain": ctx.config.server.full_url() - }), - ..Default::default() - }, - ) - .await?; - - Ok(()) - } - - /// Sending forgot password email - /// - /// # Errors - /// - /// When email sending is failed - pub async fn forgot_password(ctx: &AppContext, user: &users::Model) -> Result<()> { - Self::mail_template( - ctx, - &forgot, - mailer::Args { - to: user.email.to_string(), - locals: json!({ - "name": user.name, - "resetToken": user.reset_token, - "domain": ctx.config.server.full_url() - }), - ..Default::default() - }, - ) - .await?; - - Ok(()) - } -} diff --git a/src/mailers/auth/forgot/html.t b/src/mailers/auth/forgot/html.t deleted file mode 100644 index 221dd60..0000000 --- a/src/mailers/auth/forgot/html.t +++ /dev/null @@ -1,11 +0,0 @@ -; - - - Hey {{name}}, - Forgot your password? No worries! You can reset it by clicking the link below: - Reset Your Password - If you didn't request a password reset, please ignore this email. - Best regards,
The Loco Team
- - - diff --git a/src/mailers/auth/forgot/subject.t b/src/mailers/auth/forgot/subject.t deleted file mode 100644 index 4938df1..0000000 --- a/src/mailers/auth/forgot/subject.t +++ /dev/null @@ -1 +0,0 @@ -Your reset password link diff --git a/src/mailers/auth/forgot/text.t b/src/mailers/auth/forgot/text.t deleted file mode 100644 index 58c30fd..0000000 --- a/src/mailers/auth/forgot/text.t +++ /dev/null @@ -1,3 +0,0 @@ -Reset your password with this link: - -http://localhost/reset#{{resetToken}} diff --git a/src/mailers/auth/welcome/html.t b/src/mailers/auth/welcome/html.t deleted file mode 100644 index ae4c41c..0000000 --- a/src/mailers/auth/welcome/html.t +++ /dev/null @@ -1,13 +0,0 @@ -; - - - Dear {{name}}, - Welcome to Loco! You can now log in to your account. - Before you get started, please verify your account by clicking the link below: - - Verify Your Account - -

Best regards,
The Loco Team

- - - diff --git a/src/mailers/auth/welcome/subject.t b/src/mailers/auth/welcome/subject.t deleted file mode 100644 index 82cc6fb..0000000 --- a/src/mailers/auth/welcome/subject.t +++ /dev/null @@ -1 +0,0 @@ -Welcome {{name}} diff --git a/src/mailers/auth/welcome/text.t b/src/mailers/auth/welcome/text.t deleted file mode 100644 index 63beefd..0000000 --- a/src/mailers/auth/welcome/text.t +++ /dev/null @@ -1,4 +0,0 @@ -Welcome {{name}}, you can now log in. - Verify your account with the link below: - - http://localhost/verify#{{verifyToken}} diff --git a/src/mailers/mod.rs b/src/mailers/mod.rs deleted file mode 100644 index 0e4a05d..0000000 --- a/src/mailers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod auth; diff --git a/src/models/_entities/matches.rs b/src/models/_entities/matches.rs index 782103c..7cfc1a1 100644 --- a/src/models/_entities/matches.rs +++ b/src/models/_entities/matches.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "matches")] pub struct Model { - pub created_at: DateTimeWithTimeZone, - pub updated_at: DateTimeWithTimeZone, #[sea_orm(primary_key)] - pub id: i32, + pub id: u32, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, pub match_id: Option, - pub deleted_at: Option, + pub deleted_at: Option, #[sea_orm(column_type = "Float", nullable)] pub blue_probability: Option, #[sea_orm(column_type = "Float", nullable)] diff --git a/src/models/_entities/mod.rs b/src/models/_entities/mod.rs index 3c5e90f..08e04cd 100644 --- a/src/models/_entities/mod.rs +++ b/src/models/_entities/mod.rs @@ -1,9 +1,7 @@ //! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1 pub mod prelude; - pub mod matches; pub mod notes; pub mod player_elo; pub mod players; -pub mod users; diff --git a/src/models/_entities/notes.rs b/src/models/_entities/notes.rs index bbec070..3c01273 100644 --- a/src/models/_entities/notes.rs +++ b/src/models/_entities/notes.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "notes")] pub struct Model { - pub created_at: DateTimeWithTimeZone, - pub updated_at: DateTimeWithTimeZone, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, #[sea_orm(primary_key)] pub id: i32, pub title: Option, diff --git a/src/models/_entities/player_elo.rs b/src/models/_entities/player_elo.rs index 31b71cb..bacd169 100644 --- a/src/models/_entities/player_elo.rs +++ b/src/models/_entities/player_elo.rs @@ -6,15 +6,13 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "player_elo")] pub struct Model { - pub created_at: DateTimeWithTimeZone, - pub updated_at: DateTimeWithTimeZone, - #[sea_orm(primary_key)] - pub id: i32, + #[sea_orm(column_name = "entryID", primary_key)] pub entry_id: i32, - pub match_id: i32, - pub player_name: String, - pub player_elos: i32, - pub discord_id: i64, + pub match_id: Option, + pub player_name: Option, + pub player_elos: Option, + pub discord_id: Option, + pub created_at: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/src/models/_entities/players.rs b/src/models/_entities/players.rs index c3decc7..e1ce5cc 100644 --- a/src/models/_entities/players.rs +++ b/src/models/_entities/players.rs @@ -6,12 +6,12 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "players")] pub struct Model { - pub created_at: DateTimeWithTimeZone, - pub updated_at: DateTimeWithTimeZone, #[sea_orm(primary_key)] - pub id: i32, + pub id: u32, pub discord_id: Option, - pub deleted_at: Option, + pub created_at: DateTimeUtc, + pub updated_at: DateTimeUtc, + pub deleted_at: Option, pub player_name: Option, pub current_elo: Option, pub visual_rank_override: Option, diff --git a/src/models/_entities/prelude.rs b/src/models/_entities/prelude.rs index 4d864fe..95f2247 100644 --- a/src/models/_entities/prelude.rs +++ b/src/models/_entities/prelude.rs @@ -3,5 +3,4 @@ pub use super::matches::Entity as Matches; pub use super::notes::Entity as Notes; pub use super::player_elo::Entity as PlayerElo; -pub use super::players::Entity as Players; -pub use super::users::Entity as Users; +pub use super::players::Entity as Players; \ No newline at end of file diff --git a/src/models/_entities/users.rs b/src/models/_entities/users.rs deleted file mode 100644 index bb4fd10..0000000 --- a/src/models/_entities/users.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1 - -use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] -#[sea_orm(table_name = "users")] -pub struct Model { - pub created_at: DateTimeWithTimeZone, - pub updated_at: DateTimeWithTimeZone, - #[sea_orm(primary_key)] - pub id: i32, - pub pid: Uuid, - #[sea_orm(unique)] - pub email: String, - pub password: String, - #[sea_orm(unique)] - pub api_key: String, - pub name: String, - pub reset_token: Option, - pub reset_sent_at: Option, - pub email_verification_token: Option, - pub email_verification_sent_at: Option, - pub email_verified_at: Option, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} diff --git a/src/models/mod.rs b/src/models/mod.rs index ef7487f..57e8245 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,6 +1,5 @@ pub mod _entities; pub mod notes; -pub mod users; pub mod matches; pub mod players; pub mod player_elo; diff --git a/src/models/users.rs b/src/models/users.rs deleted file mode 100644 index 510802e..0000000 --- a/src/models/users.rs +++ /dev/null @@ -1,297 +0,0 @@ -use async_trait::async_trait; -use chrono::offset::Local; -use loco_rs::{auth::jwt, hash, prelude::*}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -pub use super::_entities::users::{self, ActiveModel, Entity, Model}; - -#[derive(Debug, Deserialize, Serialize)] -pub struct LoginParams { - pub email: String, - pub password: String, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct RegisterParams { - pub email: String, - pub password: String, - pub name: String, -} - -#[derive(Debug, Validate, Deserialize)] -pub struct Validator { - #[validate(length(min = 2, message = "Name must be at least 2 characters long."))] - pub name: String, - #[validate(custom = "validation::is_valid_email")] - pub email: String, -} - -impl Validatable for super::_entities::users::ActiveModel { - fn validator(&self) -> Box { - Box::new(Validator { - name: self.name.as_ref().to_owned(), - email: self.email.as_ref().to_owned(), - }) - } -} - -#[async_trait::async_trait] -impl ActiveModelBehavior for super::_entities::users::ActiveModel { - async fn before_save(self, _db: &C, insert: bool) -> Result - where - C: ConnectionTrait, - { - self.validate()?; - if insert { - let mut this = self; - this.pid = ActiveValue::Set(Uuid::new_v4()); - this.api_key = ActiveValue::Set(format!("lo-{}", Uuid::new_v4())); - Ok(this) - } else { - Ok(self) - } - } -} - -#[async_trait] -impl Authenticable for super::_entities::users::Model { - async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { - let user = users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::ApiKey, api_key) - .build(), - ) - .one(db) - .await?; - user.ok_or_else(|| ModelError::EntityNotFound) - } - - async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { - Self::find_by_pid(db, claims_key).await - } -} - -impl super::_entities::users::Model { - /// finds a user by the provided email - /// - /// # Errors - /// - /// When could not find user by the given token or DB query error - pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult { - let user = users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::Email, email) - .build(), - ) - .one(db) - .await?; - user.ok_or_else(|| ModelError::EntityNotFound) - } - - /// finds a user by the provided verification token - /// - /// # Errors - /// - /// When could not find user by the given token or DB query error - pub async fn find_by_verification_token( - db: &DatabaseConnection, - token: &str, - ) -> ModelResult { - let user = users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::EmailVerificationToken, token) - .build(), - ) - .one(db) - .await?; - user.ok_or_else(|| ModelError::EntityNotFound) - } - - /// /// finds a user by the provided reset token - /// - /// # Errors - /// - /// When could not find user by the given token or DB query error - pub async fn find_by_reset_token(db: &DatabaseConnection, token: &str) -> ModelResult { - let user = users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::ResetToken, token) - .build(), - ) - .one(db) - .await?; - user.ok_or_else(|| ModelError::EntityNotFound) - } - - /// finds a user by the provided pid - /// - /// # Errors - /// - /// When could not find user or DB query error - pub async fn find_by_pid(db: &DatabaseConnection, pid: &str) -> ModelResult { - let parse_uuid = Uuid::parse_str(pid).map_err(|e| ModelError::Any(e.into()))?; - let user = users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::Pid, parse_uuid) - .build(), - ) - .one(db) - .await?; - user.ok_or_else(|| ModelError::EntityNotFound) - } - - /// finds a user by the provided api key - /// - /// # Errors - /// - /// When could not find user by the given token or DB query error - pub async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { - let user = users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::ApiKey, api_key) - .build(), - ) - .one(db) - .await?; - user.ok_or_else(|| ModelError::EntityNotFound) - } - - /// Verifies whether the provided plain password matches the hashed password - /// - /// # Errors - /// - /// when could not verify password - #[must_use] - pub fn verify_password(&self, password: &str) -> bool { - hash::verify_password(password, &self.password) - } - - /// Asynchronously creates a user with a password and saves it to the - /// database. - /// - /// # Errors - /// - /// When could not save the user into the DB - pub async fn create_with_password( - db: &DatabaseConnection, - params: &RegisterParams, - ) -> ModelResult { - let txn = db.begin().await?; - - if users::Entity::find() - .filter( - model::query::condition() - .eq(users::Column::Email, ¶ms.email) - .build(), - ) - .one(&txn) - .await? - .is_some() - { - return Err(ModelError::EntityAlreadyExists {}); - } - - let password_hash = - hash::hash_password(¶ms.password).map_err(|e| ModelError::Any(e.into()))?; - let user = users::ActiveModel { - email: ActiveValue::set(params.email.to_string()), - password: ActiveValue::set(password_hash), - name: ActiveValue::set(params.name.to_string()), - ..Default::default() - } - .insert(&txn) - .await?; - - txn.commit().await?; - - Ok(user) - } - - /// Creates a JWT - /// - /// # Errors - /// - /// when could not convert user claims to jwt token - pub fn generate_jwt(&self, secret: &str, expiration: &u64) -> ModelResult { - Ok(jwt::JWT::new(secret).generate_token(expiration, self.pid.to_string(), None)?) - } -} - -impl super::_entities::users::ActiveModel { - /// Sets the email verification information for the user and - /// updates it in the database. - /// - /// This method is used to record the timestamp when the email verification - /// was sent and generate a unique verification token for the user. - /// - /// # Errors - /// - /// when has DB query error - pub async fn set_email_verification_sent( - mut self, - db: &DatabaseConnection, - ) -> ModelResult { - self.email_verification_sent_at = ActiveValue::set(Some(Local::now().into())); - self.email_verification_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); - Ok(self.update(db).await?) - } - - /// Sets the information for a reset password request, - /// generates a unique reset password token, and updates it in the - /// database. - /// - /// This method records the timestamp when the reset password token is sent - /// and generates a unique token for the user. - /// - /// # Arguments - /// - /// # Errors - /// - /// when has DB query error - pub async fn set_forgot_password_sent(mut self, db: &DatabaseConnection) -> ModelResult { - self.reset_sent_at = ActiveValue::set(Some(Local::now().into())); - self.reset_token = ActiveValue::Set(Some(Uuid::new_v4().to_string())); - Ok(self.update(db).await?) - } - - /// Records the verification time when a user verifies their - /// email and updates it in the database. - /// - /// This method sets the timestamp when the user successfully verifies their - /// email. - /// - /// # Errors - /// - /// when has DB query error - pub async fn verified(mut self, db: &DatabaseConnection) -> ModelResult { - self.email_verified_at = ActiveValue::set(Some(Local::now().into())); - Ok(self.update(db).await?) - } - - /// Resets the current user password with a new password and - /// updates it in the database. - /// - /// This method hashes the provided password and sets it as the new password - /// for the user. - /// # Errors - /// - /// when has DB query error or could not hashed the given password - pub async fn reset_password( - mut self, - db: &DatabaseConnection, - password: &str, - ) -> ModelResult { - self.password = - ActiveValue::set(hash::hash_password(password).map_err(|e| ModelError::Any(e.into()))?); - self.reset_token = ActiveValue::Set(None); - self.reset_sent_at = ActiveValue::Set(None); - Ok(self.update(db).await?) - } -} diff --git a/src/views/auth.rs b/src/views/auth.rs deleted file mode 100644 index 2240a50..0000000 --- a/src/views/auth.rs +++ /dev/null @@ -1,23 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::models::_entities::users; - -#[derive(Debug, Deserialize, Serialize)] -pub struct LoginResponse { - pub token: String, - pub pid: String, - pub name: String, - pub is_verified: bool, -} - -impl LoginResponse { - #[must_use] - pub fn new(user: &users::Model, token: &String) -> Self { - Self { - token: token.to_string(), - pid: user.pid.to_string(), - name: user.name.clone(), - is_verified: user.email_verified_at.is_some(), - } - } -} diff --git a/src/views/mod.rs b/src/views/mod.rs deleted file mode 100644 index f9bae3d..0000000 --- a/src/views/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod auth; -pub mod user; diff --git a/src/views/user.rs b/src/views/user.rs deleted file mode 100644 index 9d83041..0000000 --- a/src/views/user.rs +++ /dev/null @@ -1,21 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::models::_entities::users; - -#[derive(Debug, Deserialize, Serialize)] -pub struct CurrentResponse { - pub pid: String, - pub name: String, - pub email: String, -} - -impl CurrentResponse { - #[must_use] - pub fn new(user: &users::Model) -> Self { - Self { - pid: user.pid.to_string(), - name: user.name.clone(), - email: user.email.clone(), - } - } -} diff --git a/src/workers/downloader.rs b/src/workers/downloader.rs index 42c0bd7..6081147 100644 --- a/src/workers/downloader.rs +++ b/src/workers/downloader.rs @@ -4,8 +4,6 @@ use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use tokio::time::sleep; -use crate::models::users; - pub struct DownloadWorker { pub ctx: AppContext, } @@ -30,13 +28,7 @@ impl worker::Worker for DownloadWorker { sleep(Duration::from_millis(2000)).await; - let all = users::Entity::find() - .all(&self.ctx.db) - .await - .map_err(Box::from)?; - for user in &all { - println!("user: {}", user.id); - } + println!("================================================"); Ok(()) } From 6f894ce7e91bab94e7c1937474172a29ac858a73 Mon Sep 17 00:00:00 2001 From: "MAINMETALGEAR\\ededd" Date: Fri, 13 Dec 2024 10:53:38 -0500 Subject: [PATCH 4/4] don't commit sensitive creds to github --- .devcontainer/.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/.env b/.devcontainer/.env index a58979f..1a3ca5b 100644 --- a/.devcontainer/.env +++ b/.devcontainer/.env @@ -1,6 +1,6 @@ POSTGRES_DB=loco_app POSTGRES_USER=loco POSTGRES_PASSWORD=loco -DATABASE_URL=mysql://readonly:readonly_user_breaking_points_fan_tfpugs_online@tfpugs-mysql.cw57ix0bewxi.us-east-1.rds.amazonaws.com:3306/tfpugs +DATABASE_URL=postgres://loco:loco@db:5432/loco_app REDIS_URL=redis://redis:6379 MAILER_HOST=mailer \ No newline at end of file