diff --git a/Cargo.toml b/Cargo.toml index 06271319..07d87a16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,25 +4,26 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = {version="0.7.5", features=["ws"]} +axum = { version="0.7.5", features=["ws"] } rustrict = "0.7.24" -tokio = {version = "1.36.0", features=["full"]} +tokio = { version = "1.36.0", features=["full"] } pulldown-cmark = "0.10.0" -tokio-tungstenite = {version="0.21.0", features=["__rustls-tls"]} +tokio-tungstenite = { version="0.21.0", features=["__rustls-tls"] } futures-util = "0.3.30" headers = "0.4.0" -tracing-subscriber = {version="0.3.18", features=["env-filter"]} -axum-extra = {version="0.9.3", features=["typed-header"]} +tracing-subscriber = { version="0.3.18", features=["env-filter"] } +axum-extra = { version="0.9.3", features=["typed-header"] } tracing = "0.1.40" futures = "0.3.30" chrono = { version = "0.4.37", features = ["serde", "wasmbind"] } -tower-http = {version="0.5.2", features=["fs", "trace"]} +tower-http = { version="0.5.2", features=["fs", "trace"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -postgres = "0.19.7" lazy_static = "1.4.0" once_cell = "1.19.0" ammonia = "4.0.0" kuchikiki = "0.8.2" js-sys = "0.3.69" -uuid = { version = "1.8.0", features = ["v4", "fast-rng"] } \ No newline at end of file +uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] } +sea-orm = { version = "0.12", features = ["sqlx-postgres", "runtime-tokio-rustls", "macros", "with-uuid", "with-json"] } + diff --git a/TODO.md b/TODO.md index c4b478cd..9c0fd625 100644 --- a/TODO.md +++ b/TODO.md @@ -3,3 +3,6 @@ - [x] Rewrite that axum example to not be an example - [ ] Make messaging good - [ ] Add authentication and account system + + +NOTE: authentication stores uuid, username, email, and password \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index fcb91027..ff6d8872 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,44 +1,36 @@ mod message; mod user; -use message::model::*; -use message::func::{ into_censored_md, VecWithHardLimit }; -use user::model::*; +use message::{ model::*, func::{ into_censored_md, VecWithHardLimit } }; +use message::; +use user::{ model::*, auth::* }; use chrono::Utc; use axum::{ - extract::{ State, ws::{ Message, WebSocket, WebSocketUpgrade } }, + extract::{ connect_info::ConnectInfo, State, ws::{ Message, WebSocket, WebSocketUpgrade } }, response::IntoResponse, routing::get, Router, }; use pulldown_cmark::{ Parser, html::push_html }; use ammonia::clean; - use std::{ net::SocketAddr, path::PathBuf, collections::HashSet, sync::{ Arc, Mutex } }; use once_cell::sync::Lazy; use tokio::sync::broadcast; use tower_http::{ services::ServeDir, trace::{ DefaultMakeSpan, TraceLayer } }; - use tracing_subscriber::{ layer::SubscriberExt, util::SubscriberInitExt }; - -//allows to extract the IP of connecting user -use axum::extract::connect_info::ConnectInfo; - -//allows to split the websocket stream into separate TX and RX branches use futures::{ sink::SinkExt, stream::StreamExt }; -use postgres::Client; -use lazy_static::lazy_static; - -lazy_static! { - static ref DB_CLIENT: Mutex = Mutex::new( - Client::connect("postgres://user:password@localhost/database", postgres::NoTls).unwrap() - ); -} - static MESSAGES: Lazy>> = Lazy::new(|| Mutex::new(Vec::with_capacity(20))); static USER_ID: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(0))); +lazy_static::lazy_static! { + static ref DB_CLIENT: Arc> = { + tokio::runtime::Runtime::new().unwrap().block_on(async { + DatabaseConnectix::default(); + }) + }; +} + // Our shared state struct AppState { // We require unique usernames. This tracks which usernames have been taken. diff --git a/src/user/auth.rs b/src/user/auth.rs new file mode 100644 index 00000000..9206166f --- /dev/null +++ b/src/user/auth.rs @@ -0,0 +1,39 @@ +use lazy_static::lazy_static; +use std::error::Error; +use sea_orm::*; +use crate::user::model::{Model, Entity as ModelEntity, Column as ModelColumn}; + +pub struct DatabaseConnectix { + connection: DatabaseConnection +} + +impl Default for DatabaseConnectix { + pub async fn default() -> Self { + let uri = std::env::var("DB_URL").unwrap(); + let db: DatabaseConnection = Database::connect(uri).await?; + + return Self { + connection: db + }; + } +} + +impl DatabaseConnectix { + pub async fn new(uri: &str) -> Self { + let db: DatabaseConnection = Database::connect(uri).await?; + + return Self { + connection: db + }; + } + + /// Gets a possible user id (if one exists) for a username. + pub async fn get_user_id(&self, name: &str) -> Result> { + if let Some(res) = ModelEntity::find().expr(Expr::col("id").max()).filter(ModelColumn::Name.eq(name)).one(self.connection).await? { + if res.id == 9999 { return Box::new(Err("username is taken")); } + Ok(res.id+1) + } else { + Ok(0001) + } + } +} \ No newline at end of file diff --git a/src/user/mod.rs b/src/user/mod.rs index 99bbd124..3fd21deb 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1 +1,2 @@ -pub mod model; \ No newline at end of file +pub mod model; +pub mod auth; \ No newline at end of file diff --git a/src/user/model.rs b/src/user/model.rs index e3835079..564132b9 100644 --- a/src/user/model.rs +++ b/src/user/model.rs @@ -2,16 +2,59 @@ use std::error::Error; use rustrict::{Censor, Type}; use serde::{Serialize, Deserialize}; use uuid::Uuid; +use sea_orm::prelude::*; /// What am I? /// A class meant to hold all the values the server uses to compute messages. /// *Do not send me. Ever.* #[derive(Clone)] pub struct User { + /// Why is the user id (the number after the @) not stored here? + /// Because we can simplify this! Use the method `get_name_split()`. pub name: String, - pub id: i32, + pub uuid: Uuid pub glass: GlassModeration, - //pub sendable_user: SendableUser + pub sendable_user: SendableUser, + // pub password: String, + // pub email: String +} + +impl User { + pub fn new(name: String) -> Self { + Self { + name, + uuid: Uuid::new_v4(), + glass: GlassModeration::default(), + sendable_user: SendableUser::new(name, self.uuid) + } + } + + /// I exist because the name and id are merged into the name variable. + /// I return them seperately! + pub fn name_split(&self) -> (&str, &str) { + self.name.as_str().rsplit_once('@').unwrap() + } +} + +/// What am I? +/// A struct so that we can save user data in the database. +#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, DeriveEntityModel)] +#[sea_orm(table_name = "users")] +pub struct Model { + #[sea_orm(column_name = "id", enum_name = "Id")] + pub id: i32, + #[sea_orm(column_name = "name", enum_name = "Name")] + pub name: String, + #[sea_orm(column_name = "uuid", enum_name = "UUID")] + pub uuid: Uuid, + #[sea_orm(column_name = "password", enum_name = "Password")] + pub password: String, + #[sea_orm(column_name = "email", enum_name = "Email")] + pub email: String, + #[sea_orm(column_name = "mod", enum_name = "Mod")] + /// This is just the DB equivalent of `glass`. + /// It's in JSON format. + pub moderation_stats: String } /// What am I? @@ -19,16 +62,14 @@ pub struct User { #[derive(Serialize, Deserialize, Clone)] pub struct SendableUser { pub name: String, - pub id: i32, pub uuid: Uuid } -impl User { - pub fn new(name: String, id: i32) -> Self { +impl SendableUser { + pub fn new(name: String, uuid: Uuid) -> Self { Self { name, - id, - glass: GlassModeration::default() + uuid } } }