diff --git a/Cargo.toml b/Cargo.toml index e043016..11d288c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,11 +3,16 @@ name = "rust_forum" version = "0.1.0" edition = "2021" +[workspace] +members = [".", "entity", "migration"] + [dependencies] +entity = { path = "entity" } +migration = { path = "migration" } axum = "0.7.9" chrono = { version = "0.4.38", features = ["serde"] } +sea-orm = { version = "1.1.2", features = ["macros", "runtime-tokio-rustls", "sqlx-postgres", "with-chrono", "with-json"] } serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" -surrealdb = "2.1.1" tokio = { version = "1.41.1", features = ["full"] } tower-http = { version = "0.6.2", features = ["cors"] } diff --git a/configs/database_config.toml b/configs/database_config.toml index 4bb483e..3f790de 100644 --- a/configs/database_config.toml +++ b/configs/database_config.toml @@ -1,6 +1,6 @@ [database_config] -address = "localhost:8000" +address = "localhost:5432" username = "root" password = "root" -namespace = "rust_forum" -database = "rust_forum" \ No newline at end of file +database = "rust_forum" +backend = "postgres" \ No newline at end of file diff --git a/entity/Cargo.toml b/entity/Cargo.toml new file mode 100644 index 0000000..f72ff70 --- /dev/null +++ b/entity/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "entity" +version = "0.1.0" +edition = "2021" + +[dependencies] +sea-orm = "1.1.2" \ No newline at end of file diff --git a/entity/src/lib.rs b/entity/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/entity/src/lib.rs @@ -0,0 +1 @@ + diff --git a/migration/Cargo.toml b/migration/Cargo.toml new file mode 100644 index 0000000..c1dd07b --- /dev/null +++ b/migration/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } + +[dependencies.sea-orm-migration] +version = "1.1.0" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + "sqlx-postgres", # `DATABASE_DRIVER` feature +] diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 0000000..3b438d8 --- /dev/null +++ b/migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/migration/src/lib.rs b/migration/src/lib.rs new file mode 100644 index 0000000..aa3add6 --- /dev/null +++ b/migration/src/lib.rs @@ -0,0 +1,12 @@ +pub use sea_orm_migration::prelude::*; + +mod m20241202_201137_create_user_table; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(m20241202_201137_create_user_table::Migration)] + } +} diff --git a/migration/src/m20241202_201137_create_user_table.rs b/migration/src/m20241202_201137_create_user_table.rs new file mode 100644 index 0000000..fdb18a6 --- /dev/null +++ b/migration/src/m20241202_201137_create_user_table.rs @@ -0,0 +1,69 @@ +use extension::postgres::Type; +use sea_orm::{EnumIter, Iterable}; +use sea_orm_migration::{prelude::*, schema::*}; + +#[derive(DeriveIden)] +struct Role; + +#[derive(Iden, EnumIter)] +enum RoleType { + #[iden = "Default"] + Default, + #[iden = "Admin"] + Admin, +} + +#[derive(DeriveIden)] +enum User { + Table, + Id, + Name, + Surname, + Gender, + BirthDate, + Email, + Role, +} + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_type( + Type::create() + .as_enum(Role) + .values(RoleType::iter()) + .to_owned(), + ) + .await?; + manager + .create_table( + Table::create() + .table(User::Table) + .if_not_exists() + .col(pk_auto(User::Id)) + .col(string(User::Name)) + .col(string(User::Surname)) + .col(string(User::Email)) + .col(date_time(User::BirthDate)) + .col(boolean(User::Gender)) + .col(enumeration( + User::Role, + Alias::new("role"), + RoleType::iter(), + )) + .to_owned(), + ) + .await?; + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(User::Table).to_owned()) + .await + } +} diff --git a/migration/src/main.rs b/migration/src/main.rs new file mode 100644 index 0000000..c6b6e48 --- /dev/null +++ b/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[async_std::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +} diff --git a/src/database.rs b/src/database.rs index 4d32204..9ead1ce 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,46 +1,31 @@ pub mod interaction; +pub mod message; pub mod post; pub mod user; -pub type SurrealUserReturn = Result, surrealdb::Error>; -pub type SurrealCountReturn = Result, surrealdb::Error>; +use std::time::Duration; -use std::{sync::LazyLock, time::Duration}; - -use surrealdb::{ - engine::remote::ws::{Client, Ws}, - opt::auth::Root, - Surreal, -}; +use sea_orm::{Database, DatabaseConnection}; use tokio::time::sleep; -use crate::{feature::user::User, DatabaseConfig}; +use crate::DatabaseConfig; -static DB: LazyLock> = LazyLock::new(Surreal::init); - -pub async fn establish_connection() { +pub async fn establish_connection() -> DatabaseConnection { let database_config = DatabaseConfig::default(); - - DB.connect::(database_config.address).await.unwrap(); - DB.signin(Root { - username: &database_config.username, - password: &database_config.password, - }) - .await - .unwrap(); - DB.use_ns(database_config.namespace).await.unwrap(); - DB.use_db(database_config.database).await.unwrap(); - DB.query("DEFINE INDEX email ON TABLE user FIELDS email UNIQUE;").await.unwrap(); + let connection_string = format!( + "{}://{}:{}@{}/{}", + database_config.backend, + database_config.username, + database_config.password, + database_config.address, + database_config.database + ); + Database::connect(connection_string).await.unwrap() } pub async fn is_alive() -> bool { tokio::select! { - alive_status = DB.health() => { - match alive_status { - Ok(_) => true, - Err(_) => false, - } - }, - _ = sleep(Duration::from_secs(1)) => false + + _ = sleep(Duration::from_secs(1)) => false, } } diff --git a/src/database/interaction.rs b/src/database/interaction.rs index e52e629..8b13789 100644 --- a/src/database/interaction.rs +++ b/src/database/interaction.rs @@ -1,6 +1 @@ -async fn create_interaction() {} -async fn read_interaction() {} -async fn update_interaction() {} -async fn delete_interaction() {} -async fn count_interactions_of_a_user() {} -async fn count_interactions_of_a_post() {} \ No newline at end of file + diff --git a/src/database/message.rs b/src/database/message.rs index e69de29..8b13789 100644 --- a/src/database/message.rs +++ b/src/database/message.rs @@ -0,0 +1 @@ + diff --git a/src/database/post.rs b/src/database/post.rs index 9120766..8b13789 100644 --- a/src/database/post.rs +++ b/src/database/post.rs @@ -1,5 +1 @@ -async fn create_post() {} -async fn read_post() {} -async fn update_post() {} -async fn delete_post() {} -async fn count_posts_of_a_user() {} + diff --git a/src/database/user.rs b/src/database/user.rs index 0b315e6..8b13789 100644 --- a/src/database/user.rs +++ b/src/database/user.rs @@ -1,29 +1 @@ -use crate::feature::user::User; - -use super::{SurrealCountReturn, SurrealUserReturn, DB}; - - -pub async fn create_user(user:User) -> SurrealUserReturn { - DB.create("user").content(user).await -} - -pub async fn read_user(email: &String) -> SurrealUserReturn{ - DB.select(("user", email)).await -} - -pub async fn update_user(target_user_email: &String, user: User) -> SurrealUserReturn{ - DB.update(("user", target_user_email)).content(user).await -} -pub async fn delete_user(email: &String) -> SurrealUserReturn { - DB.delete(("user", email)).await -} -pub async fn count_users() -> SurrealCountReturn{ - DB.query("SELECT count() FROM user GROUP BY count;").await?.take("count") -} -pub async fn count_male_users() -> SurrealCountReturn{ - DB.query("SELECT count() FROM user WHERE gender = true GROUP BY count;").await?.take("count") -} -pub async fn count_female_users() -> SurrealCountReturn{ - DB.query("SELECT count() FROM user WHERE gender = false GROUP BY count;").await?.take("count") -} \ No newline at end of file diff --git a/src/feature.rs b/src/feature.rs index f1bfc73..cb941d5 100644 --- a/src/feature.rs +++ b/src/feature.rs @@ -1,4 +1,4 @@ -pub mod user; +pub mod interaction; pub mod message; pub mod post; -pub mod interaction; \ No newline at end of file +pub mod user; diff --git a/src/feature/interaction.rs b/src/feature/interaction.rs index 700da59..3154916 100644 --- a/src/feature/interaction.rs +++ b/src/feature/interaction.rs @@ -1,3 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] enum InteractionType { Like, Dislike, @@ -8,9 +11,12 @@ enum InteractionType { Rocket, Smile, Laugh, + Sad, + Shrug, } +#[derive(Debug, Serialize, Deserialize)] struct Interaction { - post_id:String, - user_id:String, + post_id: String, + user_id: String, interaction_type: InteractionType, -} \ No newline at end of file +} diff --git a/src/feature/message.rs b/src/feature/message.rs index 3a90109..7fc03ba 100644 --- a/src/feature/message.rs +++ b/src/feature/message.rs @@ -1,15 +1,21 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] pub struct Message { pub sender_email: String, pub receiver_email: String, pub message: String, + pub execution_time: DateTime, } impl Message { - pub async fn new(sender_email: String, receiver_email: String, message: String) -> Self{ - Message { + pub async fn new(sender_email: String, receiver_email: String, message: String) -> Self { + Self { sender_email, receiver_email, message, + execution_time: Utc::now(), } } -} \ No newline at end of file +} diff --git a/src/feature/post.rs b/src/feature/post.rs index e69de29..d4cd769 100644 --- a/src/feature/post.rs +++ b/src/feature/post.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Post { + pub poster_email: String, + pub post: String, +} + +impl Post { + pub async fn new(poster_email: String, post: String) -> Self { + Self { poster_email, post } + } +} diff --git a/src/feature/user.rs b/src/feature/user.rs index 9072467..7fc10db 100644 --- a/src/feature/user.rs +++ b/src/feature/user.rs @@ -24,7 +24,13 @@ pub struct User { } impl User { - pub async fn new(name: Vec, surname: Vec, gender: bool, birth_date: NaiveDate, email: String) -> Self { + pub async fn new( + name: Vec, + surname: Vec, + gender: bool, + birth_date: NaiveDate, + email: String, + ) -> Self { Self { name, surname, @@ -34,4 +40,4 @@ impl User { role: Role::User, } } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 99fb3d8..2e6fe17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,8 @@ -pub mod feature; pub mod database; +pub mod feature; pub mod utils; +use sea_orm::DatabaseConnection; use utils::naive_toml_parser; const DATABASE_CONFIG_FILE_LOCATION: &str = "./configs/database_config.toml"; @@ -12,8 +13,8 @@ pub struct DatabaseConfig { pub address: String, pub username: String, pub password: String, - pub namespace: String, pub database: String, + pub backend: String, } impl Default for DatabaseConfig { fn default() -> Self { @@ -21,8 +22,8 @@ impl Default for DatabaseConfig { if header == "[database_config]" { Self { + backend: database_configs.pop().unwrap(), database: database_configs.pop().unwrap(), - namespace: database_configs.pop().unwrap(), password: database_configs.pop().unwrap(), username: database_configs.pop().unwrap(), address: database_configs.pop().unwrap(), @@ -51,3 +52,8 @@ impl Default for ServerConfig { } } } + +#[derive(Debug, Clone)] +pub struct AppState { + pub database_connection: DatabaseConnection, +}