diff --git a/src/app/callback/mod.rs b/src/app/callback/mod.rs index 9b0d03a..6d58cee 100644 --- a/src/app/callback/mod.rs +++ b/src/app/callback/mod.rs @@ -8,8 +8,8 @@ use crate::{app::{NameSchema, SignupSchema, EmailSchema, PhoneSchema, EmailContext, FormValidation, validation::ValidationSchema, PasswordSchema, VerifyPassword}, - server_function::{ConfirmSubscription, - self, Login, sign_up}}; + server_function::{routes::ConfirmSubscription, + self, routes::Login, routes::sign_up}}; use super::{VerificationValidation, SignupContext, AppState}; @@ -72,8 +72,8 @@ let signup = create_resource(cx, move || sign_up_schema.clone(), move |sign_up_s { match val { Ok(FormValidation::Success {random_string}) => { database_connection_result_setter.set(String::from("Sending Verification Email...")); - let verification = create_server_action::(cx); - verification.dispatch(crate::server_function::VerifyEmail { first_name: + let verification = create_server_action::(cx); + verification.dispatch(crate::server_function::routes::VerifyEmail { first_name: info.first_name.get().unwrap().value(), email: info.email.get().unwrap().value(), random_string: random_string.unwrap()} ); @@ -276,7 +276,7 @@ let signup = create_resource(cx, move || sign_up_schema.clone(), move |sign_up_s toggle ) { true => { - match server_function::cred_validation(cx, Some(email_schema), None) + match server_function::routes::cred_validation(cx, Some(email_schema), None) .await .unwrap() { @@ -326,7 +326,7 @@ let signup = create_resource(cx, move || sign_up_schema.clone(), move |sign_up_s toggle ) { true => { - match server_function::cred_validation(cx, None, Some(phone_schema)) + match server_function::routes::cred_validation(cx, None, Some(phone_schema)) .await .unwrap() { diff --git a/src/app/pages/components/anciliary/mod.rs b/src/app/pages/components/anciliary/mod.rs index e7c48a5..8c3249b 100644 --- a/src/app/pages/components/anciliary/mod.rs +++ b/src/app/pages/components/anciliary/mod.rs @@ -7,7 +7,7 @@ use crate::{ pages::{conversation::ConversationParams, Avatar, SettingsModal, ICONVEC, SINKVEC}, IsOpen, SideBarContext, }, - server_function::{self, login_status, UserLogin}, + server_function::{self, routes::login_status, UserLogin}, }; use super::avatar::STREAMVEC; @@ -64,7 +64,7 @@ impl<'a> SidebarIcon<'a> { create_resource( cx, || (), - async move |_| server_function::logout(cx).await.unwrap(), + async move |_| server_function::routes::logout(cx).await.unwrap(), ); // queue_microtask(move || use_navigate(cx)("/login", Default::default()).unwrap()); })), @@ -155,7 +155,7 @@ pub fn Sidebar(cx: Scope, children: Children) -> impl IntoView { fn DesktopSidebar(cx: Scope) -> impl IntoView { create_effect(cx, move |_| { spawn_local(async move { - if server_function::redirect(cx).await.unwrap() { + if server_function::routes::redirect(cx).await.unwrap() { queue_microtask(move || { leptos_router::use_navigate(cx)("/login", Default::default()).unwrap() }); diff --git a/src/app/pages/components/avatar/mod.rs b/src/app/pages/components/avatar/mod.rs index 96c1deb..278a97a 100644 --- a/src/app/pages/components/avatar/mod.rs +++ b/src/app/pages/components/avatar/mod.rs @@ -1,7 +1,7 @@ use crate::app::pages::{ components::anciliary::loading_fallback, HandleWebSocket, StreamData, SyncChannel, WsData, }; -use crate::server_function::get_icon; +use crate::server_function::routes::get_icon; use base64::{engine::general_purpose, Engine}; use lazy_static::lazy_static; use leptos::*; diff --git a/src/app/pages/components/modal/mod.rs b/src/app/pages/components/modal/mod.rs index 1b58a97..2d472c9 100644 --- a/src/app/pages/components/modal/mod.rs +++ b/src/app/pages/components/modal/mod.rs @@ -14,7 +14,9 @@ use crate::app::{ }; use crate::server_function::{ - delete_conversations, get_users, login_status, upload_user_info, CreateGroupConversation, + routes::{ + delete_conversations, get_users, login_status, upload_user_info, CreateGroupConversation, + }, UserModel, }; diff --git a/src/app/pages/conversation/mod.rs b/src/app/pages/conversation/mod.rs index 0de90c4..03eb528 100644 --- a/src/app/pages/conversation/mod.rs +++ b/src/app/pages/conversation/mod.rs @@ -14,9 +14,13 @@ use crate::{ DrawerContext, IsOpen, MessageDrawerContext, SeenContext, SeenContextInner, }, server_function::{ - self, find_image, get_conversations, handle_seen, login_status, validate_conversation, - view_messages, ConversationMeta, ImageAvailability, MergedConversation, MergedMessages, - SeenMessageFacing, UserLogin, + self, + routes::{ + find_image, get_conversations, handle_seen, login_status, validate_conversation, + view_messages, + }, + ConversationMeta, ImageAvailability, MergedConversation, MergedMessages, SeenMessageFacing, + UserLogin, }, }; @@ -414,7 +418,6 @@ fn Body(cx: Scope, messages: Vec) -> impl IntoView { { seen_context.get().get(index).unwrap().last_message_id } else { - let last_message = if let Some(last_message) = boxed_messages.clone().iter().last() { last_message.message_id } else { diff --git a/src/app/pages/mod.rs b/src/app/pages/mod.rs index 0ea81b3..2201fb0 100644 --- a/src/app/pages/mod.rs +++ b/src/app/pages/mod.rs @@ -1,6 +1,6 @@ use crate::{ app::pages::components::{anciliary::UserContext, avatar}, - server_function::get_image, + server_function::routes::get_image, }; pub mod components; diff --git a/src/app/pages/users/mod.rs b/src/app/pages/users/mod.rs index fb5b7b6..20df4d4 100644 --- a/src/app/pages/users/mod.rs +++ b/src/app/pages/users/mod.rs @@ -6,7 +6,9 @@ use crate::{ components::anciliary::{loading_fallback, EmptyState, Sidebar, UserContexts}, Avatar, ICONVEC, SINKVEC, STREAMVEC, }, - server_function::{associated_conversation, conversation_action, get_users, UserModel}, + server_function::{ + routes::associated_conversation, routes::conversation_action, routes::get_users, UserModel, + }, }; #[component] diff --git a/src/app/pages/websocket/mod.rs b/src/app/pages/websocket/mod.rs index 53aa7ca..75e10af 100644 --- a/src/app/pages/websocket/mod.rs +++ b/src/app/pages/websocket/mod.rs @@ -14,7 +14,7 @@ use super::components::avatar::{self, IconData, SINKVEC, STREAMVEC}; use super::conversation::Message; use super::UserContext; use crate::app::{pages::components::avatar::ToStreamData, SeenContext}; -use crate::server_function::handle_message_input; +use crate::server_function::routes::handle_message_input; #[derive(Debug, Clone)] pub enum SyncChannel { diff --git a/src/server_function/mod.rs b/src/server_function/mod.rs index 2248072..07ddac8 100644 --- a/src/server_function/mod.rs +++ b/src/server_function/mod.rs @@ -3,6 +3,8 @@ use iter_tools::Itertools; use leptos::*; use serde::{Deserialize, Serialize}; +pub mod routes; + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct UserModel { pub id: i32, @@ -149,10 +151,6 @@ pub struct MessageStructFacing { pub last_name: String, } -use crate::app::{EmailSchema, PhoneSchema, VerificationValidation, VerifyPassword}; - -#[cfg(feature = "ssr")] -use crate::app::FormValidation; #[cfg(feature = "ssr")] use crate::entities::{conversation, user_conversation}; @@ -163,1390 +161,248 @@ enum UserValidation { } cfg_if::cfg_if! { -if #[cfg(feature = "ssr")] { - - use super::entities::prelude::*; - use super::entities::*; - use sea_orm::*; - -struct RetrieveConversations; - - #[derive(Debug, sea_orm::FromQueryResult, Serialize, Clone)] - struct MessageInfo { - conversation_id: i32, - name: Option, - is_group: bool - } - - #[derive(Debug, sea_orm::FromQueryResult)] - struct ConversationInfo { - conversation_id: i32, - user_ids: i32, - first_name: String, - last_name: String, - email: String - } - - #[derive(Debug, sea_orm::FromQueryResult, Serialize, Clone, PartialEq, Deserialize)] - pub struct SeenMessageStruct { - seen_id: Option, - message_id: Option, - first_name: Option, - last_name: Option, - } - - impl From for SeenMessageFacing { - fn from(value: SeenMessageStruct) -> Self { - Self { - seen_id: value.seen_id, - message_id: value.message_id, - first_name: value.first_name, - last_name: value.last_name - } - } - } - - #[derive(Debug, sea_orm::FromQueryResult, Serialize, Clone, PartialEq, Deserialize)] - pub struct MessageStruct { - pub message_id: i32, - pub message_body: Option, - pub message_image: Option, - pub message_created_at: sea_orm::prelude::DateTimeUtc, - pub message_conversation_id: i32, - pub message_sender_id: i32, - pub first_name: String, - pub last_name: String - } - - impl From for MessageStructFacing { - fn from(value: MessageStruct) -> Self { - Self { - message_id: value.message_id, - message_body: value.message_body, - message_sender_id: value.message_sender_id, - message_image: value.message_image, - message_created_at: value.message_created_at.to_string(), - message_conversation_id: value.message_conversation_id, - first_name: value.first_name, - last_name: value.last_name - } - } + if #[cfg(feature = "ssr")] { + + use super::entities::prelude::*; + use super::entities::*; + use sea_orm::*; + + struct RetrieveConversations; + + #[derive(Debug, sea_orm::FromQueryResult, Serialize, Clone)] + struct MessageInfo { + conversation_id: i32, + name: Option, + is_group: bool } - - impl From for FacingMessageInfo { - fn from(value: ConversationInfo) -> Self { - Self { - conversation_id: value.conversation_id, - user_ids: vec![value.user_ids], - first_name: value.first_name, - last_name: value.last_name, - email: value.email + + #[derive(Debug, sea_orm::FromQueryResult)] + struct ConversationInfo { + conversation_id: i32, + user_ids: i32, + first_name: String, + last_name: String, + email: String + } + + #[derive(Debug, sea_orm::FromQueryResult, Serialize, Clone, PartialEq, Deserialize)] + pub struct SeenMessageStruct { + seen_id: Option, + message_id: Option, + first_name: Option, + last_name: Option, + } + + impl From for SeenMessageFacing { + fn from(value: SeenMessageStruct) -> Self { + Self { + seen_id: value.seen_id, + message_id: value.message_id, + first_name: value.first_name, + last_name: value.last_name + } } - } - } - - -impl RetrieveConversations { - - async fn retrieve_user_conversations(user: &UserLogin, data: &sea_orm::DatabaseConnection) -> Vec { - UserConversation::find() - .filter(user_conversation::server::Column::UserIds.eq(user.id)) - .columns::>(vec![ - crate::entities::conversation::server::Column::Id, - crate::entities::conversation::server::Column::Name, - crate::entities::conversation::server::Column::IsGroup, - ]) - .inner_join(Conversation) - .into_model::() - .all(data) - .await - .unwrap() - } - - async fn retrieve_associated_users(_user: UserLogin, data: &sea_orm::DatabaseConnection, condition: sea_orm::Condition) -> Vec { - - let associated_users = UserConversation::find() - .filter(condition) - .inner_join(Users) - .columns::>(vec![ - user_conversation::server::Column::UserIds, - user_conversation::server::Column::ConversationId, - ]) - .columns::>(vec![ - crate::entities::users::server::Column::Id, - crate::entities::users::server::Column::FirstName, - crate::entities::users::server::Column::LastName, - crate::entities::users::server::Column::Email, - ]) - .columns::>(vec![ - crate::entities::conversation::server::Column::IsGroup, - ]) - .inner_join(Conversation) - .into_model::() - .all(data) - .await - .unwrap(); - - associated_users - .into_iter() - .map_into() - .collect() - } - - async fn retrieve_messages(conversations: &Vec, data: &sea_orm::DatabaseConnection) -> Vec { - let mut condition: Condition = Condition::any(); - for conversation in conversations { - condition = condition.add(message::server::Column::MessageConversationId.eq(*conversation)); + } + + #[derive(Debug, sea_orm::FromQueryResult, Serialize, Clone, PartialEq, Deserialize)] + pub struct MessageStruct { + pub message_id: i32, + pub message_body: Option, + pub message_image: Option, + pub message_created_at: sea_orm::prelude::DateTimeUtc, + pub message_conversation_id: i32, + pub message_sender_id: i32, + pub first_name: String, + pub last_name: String + } + + impl From for MessageStructFacing { + fn from(value: MessageStruct) -> Self { + Self { + message_id: value.message_id, + message_body: value.message_body, + message_sender_id: value.message_sender_id, + message_image: value.message_image, + message_created_at: value.message_created_at.to_string(), + message_conversation_id: value.message_conversation_id, + first_name: value.first_name, + last_name: value.last_name + } } - - Message::find().filter(condition).inner_join(Users).columns::>(vec![ - crate::entities::users::server::Column::FirstName, - crate::entities::users::server::Column::LastName, - ]) - .order_by_asc(message::server::Column::MessageCreatedAt).into_model::().all(data) - .await.unwrap().into_iter().map_into().collect() } - - async fn retrieve_seen(messages: &Vec, data: &sea_orm::DatabaseConnection) -> Vec { - use crate::entities::seen_messages; - - let mut condition: Condition = Condition::any(); - for message in messages { - condition = condition.add(seen_messages::server::Column::MessageId.eq(message.message_id)); + + impl From for FacingMessageInfo { + fn from(value: ConversationInfo) -> Self { + Self { + conversation_id: value.conversation_id, + user_ids: vec![value.user_ids], + first_name: value.first_name, + last_name: value.last_name, + email: value.email + } } - - SeenMessages::find().filter(condition) - .columns::>(vec![ - crate::entities::users::server::Column::Id, - crate::entities::users::server::Column::FirstName, - crate::entities::users::server::Column::LastName, - ]) - .join(JoinType::LeftJoin, seen_messages::server::Relation::Users.def()).into_model::() - .all(data).await.unwrap().into_iter().map_into().collect() - - } - - async fn retrieve_images(user_id: i32, data: &sea_orm::DatabaseConnection) -> Option { - Users::find().filter(users::server::Column::Id.eq(user_id)).one(data).await.unwrap().unwrap().image - } - -} - -pub struct AppendDatabase; - -impl AppendDatabase { - - async fn insert_messages(data: &sea_orm::DatabaseConnection, message_model: crate::entities::message::server::ActiveModel) { - let inserted_message = Message::insert(message_model.clone()).exec(data).await.unwrap(); - - SeenMessages::insert(seen_messages::server::ActiveModel { - message_id: ActiveValue::Set(inserted_message.last_insert_id), - seen_id: message_model.message_sender_id - }).exec(data).await.unwrap(); - - } - - async fn insert_seen(data: &DatabaseConnection, message_model: Vec, user_id: i32) { - let existing_ids = SeenMessages::find() - .filter(seen_messages::server::Column::MessageId.is_in(message_model.clone())) - .filter(seen_messages::server::Column::SeenId.eq(user_id)) - .all(data) - .await - .unwrap(); - - let existing_ids: Vec = existing_ids.iter().map(|row| row.message_id).collect(); - - let new_ids: Vec = message_model - .iter() - .filter(|&message_id| !existing_ids.contains(message_id)) - .copied() - .collect(); - - if !new_ids.is_empty() { - let insert_data: Vec = new_ids - .iter() - .map(|&message_id| seen_messages::server::ActiveModel { - message_id: ActiveValue::Set(message_id), - seen_id: ActiveValue::Set(user_id), - }) - .collect(); - SeenMessages::insert_many(insert_data) - .exec(data) + } + + + impl RetrieveConversations { + + async fn retrieve_user_conversations(user: &UserLogin, data: &sea_orm::DatabaseConnection) -> Vec { + UserConversation::find() + .filter(user_conversation::server::Column::UserIds.eq(user.id)) + .columns::>(vec![ + crate::entities::conversation::server::Column::Id, + crate::entities::conversation::server::Column::Name, + crate::entities::conversation::server::Column::IsGroup, + ]) + .inner_join(Conversation) + .into_model::() + .all(data) + .await + .unwrap() + } + + async fn retrieve_associated_users(_user: UserLogin, data: &sea_orm::DatabaseConnection, condition: sea_orm::Condition) -> Vec { + + let associated_users = UserConversation::find() + .filter(condition) + .inner_join(Users) + .columns::>(vec![ + user_conversation::server::Column::UserIds, + user_conversation::server::Column::ConversationId, + ]) + .columns::>(vec![ + crate::entities::users::server::Column::Id, + crate::entities::users::server::Column::FirstName, + crate::entities::users::server::Column::LastName, + crate::entities::users::server::Column::Email, + ]) + .columns::>(vec![ + crate::entities::conversation::server::Column::IsGroup, + ]) + .inner_join(Conversation) + .into_model::() + .all(data) .await .unwrap(); + + associated_users + .into_iter() + .map_into() + .collect() } - } - - async fn delete_conversation(conversation_id: i32, data: &sea_orm::DatabaseConnection, user: UserLogin) { - if let Ok(conversation) = Conversation::find(). - filter(Condition::all() - .add(conversation::server::Column::Id.eq(conversation_id)) - .add(user_conversation::server::Column::UserIds.eq(user.id))) - .reverse_join(UserConversation) - .one(data).await { - conversation.unwrap().delete(data).await.unwrap(); - } - } - - async fn modify(user: UserLogin, image: Option, data: &sea_orm::DatabaseConnection, first_name: Option, last_name: Option) { - let mut user_model: users::server::ActiveModel = Users::find_by_id(user.id).one(data).await.unwrap().unwrap().into(); - if let Some(image_path) = image { - user_model.image = Set(Some(image_path)); - } - - if let Some(first_name) = first_name { - user_model.first_name = Set(first_name); - } - - if let Some(last_name) = last_name { - user_model.last_name = Set(last_name); - } - - Users::update(user_model).exec(data).await.unwrap(); - } - -} -} -} - -#[server(SignUp, "/api", "Url")] -pub async fn sign_up( - cx: Scope, - form: crate::app::SignupSchema, -) -> Result { - use super::entities::{prelude::*, *}; - use argon2::{ - password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, - Argon2, - }; - use rand::Rng; - use sea_orm::*; - - let struct_vector: Vec> = vec![ - Box::new(form.first_name.clone()), - Box::new(form.last_name.clone()), - Box::new(form.email.clone()), - Box::new(form.password.clone()), - Box::new(form.phone_number.clone()), - ]; - - if struct_vector.iter().any(|item| item.validate().is_err()) { - Ok(crate::app::FormValidation::Error) - } else { - // if there is an email entry, then return - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>| { - let form = form.clone(); - let entry = form.email.entry.clone(); - - async move { - if Users::find() - .filter(users::server::Column::Email.eq(entry.clone())) - .one(&data.lock().await.connection) - .await? - .is_some() - { - Ok(crate::app::FormValidation::EmailPresent) - } else if Users::find() - .filter( - users::server::Column::PhoneNumber.eq(form - .phone_number - .entry - .replace('+', "") - .parse::()?), - ) - .one(&data.lock().await.connection) - .await? - .is_some() - { - Ok(super::app::FormValidation::PhonePresent) - } else { - let special_characters = "!@#$%^&*"; - - // Generate a random 15-letter string with lowercase, uppercase, and special characters - let mut rng = rand::thread_rng(); - let random_string: String = (0..15) - .map(|_| { - let charset: Vec = match rng.gen_range(0..3) { - 0 => (b'a'..=b'z').collect(), - 1 => (b'A'..=b'Z').collect(), - _ => special_characters.bytes().collect(), - }; - char::from(charset[rng.gen_range(0..charset.len())]) - }) - .collect(); - - let new_user = temp_users::server::ActiveModel { - first_name: ActiveValue::Set(form.first_name.entry.clone()), - last_name: ActiveValue::Set(form.last_name.entry), - email: ActiveValue::Set(form.email.entry.clone()), - phone_number: ActiveValue::Set( - form.phone_number - .entry - .chars() - .filter(|c| c.is_ascii_digit()) - .collect::() - .parse::()?, - ), - password: ActiveValue::Set({ - let salt = SaltString::generate(&mut OsRng); - let argon2 = Argon2::default(); - argon2 - .hash_password(form.password.entry.as_bytes(), &salt) - .unwrap() - .to_string() - }), - verification: { - // Define the special characters to include in the random string - - ActiveValue::Set(random_string.clone()) - }, - time: ActiveValue::Set(chrono::Utc::now()), - ..Default::default() - }; - if TempUsers::insert(new_user) - .exec(&data.lock().await.connection) - .await - .is_ok() - { - Ok(super::app::FormValidation::Success { - random_string: Some(random_string), - }) - } else { - Ok(super::app::FormValidation::Error) - } + + async fn retrieve_messages(conversations: &Vec, data: &sea_orm::DatabaseConnection) -> Vec { + let mut condition: Condition = Condition::any(); + for conversation in conversations { + condition = condition.add(message::server::Column::MessageConversationId.eq(*conversation)); } + + Message::find().filter(condition).inner_join(Users).columns::>(vec![ + crate::entities::users::server::Column::FirstName, + crate::entities::users::server::Column::LastName, + ]) + .order_by_asc(message::server::Column::MessageCreatedAt).into_model::().all(data) + .await.unwrap().into_iter().map_into().collect() } - }, - ) - .await? - } -} - -#[server(Validate, "/api", "Url")] -pub async fn cred_validation( - cx: Scope, - email: Option, - phone_number: Option, -) -> Result { - use super::entities::{prelude::*, *}; - use sea_orm::*; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>| { - let email = email.clone(); - let phone_number = phone_number.clone(); - async move { - let db = &data.lock().await.connection; - if let Some(email) = email { - if TempUsers::find() - .filter(temp_users::server::Column::Email.eq(email.entry.clone())) - .one(db) - .await? - .is_some() - || Users::find() - .filter(users::server::Column::Email.eq(email.entry)) - .one(db) - .await? - .is_some() - { - Ok(FormValidation::EmailPresent) - } else { - Ok(FormValidation::Success { - random_string: None, - }) - } - } else if TempUsers::find() - .filter( - temp_users::server::Column::PhoneNumber.eq(phone_number - .clone() - .unwrap() - .entry - .replace('+', "") - .parse::() - .unwrap()), - ) - .one(db) - .await? - .is_some() - || Users::find() - .filter( - users::server::Column::PhoneNumber.eq(phone_number - .clone() - .unwrap() - .entry - .parse::() - .unwrap()), - ) - .one(db) - .await? - .is_some() - { - Ok(FormValidation::PhonePresent) - } else { - Ok(FormValidation::Success { - random_string: None, - }) - } - } - }, - ) - .await? -} - -#[server(VerifyEmail, "/api", "Url")] -pub async fn send_verification_email( - email: String, - first_name: String, - random_string: String, -) -> Result { - match crate::emailing::email_client::send_email(email, first_name, random_string) { - Ok(_) => Ok(String::from("Successful Signup")), - Err(e) => Ok(format!("Error at sending verification email: {e}")), - } -} - -#[server(ConfirmSubscription, "/api", "Url")] -pub async fn confirm_subscription( - cx: Scope, - email: String, - input: String, -) -> Result { - use super::entities::{prelude::*, *}; - use sea_orm::*; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>| { - let email = email.clone(); - let input = input.clone(); - async move { - let db = &data.lock().await.connection; - if let Ok(user) = TempUsers::find() - .filter(temp_users::server::Column::Email.eq(email)) - .one(&db.clone()) - .await - .map_err(|_| VerificationValidation::EmailNotPresent) - { - let user = user.unwrap(); - if user.verification.trim().replace('"', "") == input.trim().replace('"', "") { - let registered_user = users::server::ActiveModel { - first_name: ActiveValue::Set(user.first_name.clone()), - last_name: ActiveValue::Set(user.last_name.clone()), - email: ActiveValue::Set(user.email.clone()), - phone_number: ActiveValue::Set(user.phone_number), - password: ActiveValue::Set(user.password.clone()), - ..Default::default() - }; - - println!("Inserting into db"); - if Users::insert(registered_user.clone()) - .exec(&db.clone()) - .await - .is_ok() - { - Ok(VerificationValidation::Success) - } else { - Ok(super::app::VerificationValidation::ServerError) - } - } else { - Ok(VerificationValidation::IncorrectValidationCode) + + async fn retrieve_seen(messages: &Vec, data: &sea_orm::DatabaseConnection) -> Vec { + use crate::entities::seen_messages; + + let mut condition: Condition = Condition::any(); + for message in messages { + condition = condition.add(seen_messages::server::Column::MessageId.eq(message.message_id)); } - } else { - Ok(VerificationValidation::ServerError) + + SeenMessages::find().filter(condition) + .columns::>(vec![ + crate::entities::users::server::Column::Id, + crate::entities::users::server::Column::FirstName, + crate::entities::users::server::Column::LastName, + ]) + .join(JoinType::LeftJoin, seen_messages::server::Relation::Users.def()).into_model::() + .all(data).await.unwrap().into_iter().map_into().collect() + } - } - }, - ) - .await? -} - -#[server(Login, "/api", "Url")] -pub async fn login( - cx: Scope, - email: String, - password: String, -) -> Result { - use super::entities::{prelude::*, *}; - use actix_identity::Identity; - use actix_web::HttpMessage; - use actix_web::HttpRequest; - use argon2::{ - password_hash::{PasswordHash, PasswordVerifier}, - Argon2, - }; - use sea_orm::*; - - log!("retrieving request"); - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - request: HttpRequest| { - log!("retrieved request"); - let email = email.clone(); - let password = password.clone(); - async move { - let db = &data.lock().await.connection; - if let Some(user) = Users::find() - .filter(users::server::Column::Email.eq(email.clone())) - .one(db) - .await? - { - let parsed_hash = PasswordHash::new(&user.password).unwrap(); - match Argon2::default() - .verify_password(password.as_bytes(), &parsed_hash) - .is_ok() - { - true => { - Identity::login( - &request.extensions(), - serde_json::to_string_pretty(&UserLogin { - id: user.id, - email: user.email.clone(), - first_name: user.first_name.clone(), - last_name: user.last_name.clone(), - })?, - ) - .unwrap(); - - Ok(VerifyPassword::Success(UserLogin { - id: user.id, - email: user.email, - first_name: user.first_name, - last_name: user.last_name, - })) - } - false => Ok(VerifyPassword::IncorrectCredentials), - } - } else { - Ok(VerifyPassword::IncorrectCredentials) + + async fn retrieve_images(user_id: i32, data: &sea_orm::DatabaseConnection) -> Option { + Users::find().filter(users::server::Column::Id.eq(user_id)).one(data).await.unwrap().unwrap().image } - } - }, - ) - .await? -} - -#[server(LoginStatus, "/api", "Url")] -pub async fn login_status(cx: Scope) -> Result { - use actix_identity::Identity; - - leptos_actix::extract(cx, move |user: Option| async { - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - Ok(user) - }) - .await? -} - -#[server(Redirect, "/api", "Url")] -pub async fn redirect(cx: Scope) -> Result { - use actix_identity::Identity; - leptos_actix::extract( - cx, - move |user: Option| async move { user.is_none() }, - ) - .await -} - -// #[cfg(feature = "ssr")] -#[server(GetUsers, "/api", "Url")] -pub async fn get_users(cx: Scope) -> Result, ServerFnError> { - use super::entities::prelude::*; - use super::entities::users; - use sea_orm::*; - - Ok(leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let data = &data.lock().await.connection; - Ok(Users::find() - .order_by_asc(users::server::Column::Id) - .filter(users::server::Column::Id.ne(user.id)) - .all(data) - .await?) - } - }, - ) - .await?? - .into_iter() - .map_into() - .rev() - .collect()) -} - -#[server(GetConversations, "/api", "Url")] -pub async fn get_conversations(cx: Scope) -> Result, ServerFnError> { - use actix_identity::Identity; - use sea_orm::*; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let data = &data.lock().await.connection; - let conversations = - RetrieveConversations::retrieve_user_conversations(&user, data).await; - - let mut condition = Condition::any(); - for conversation in &conversations { - condition = condition.add( - user_conversation::server::Column::ConversationId - .eq(conversation.conversation_id), - ); + + } + + pub struct AppendDatabase; + + impl AppendDatabase { + + async fn insert_messages(data: &sea_orm::DatabaseConnection, message_model: crate::entities::message::server::ActiveModel) { + let inserted_message = Message::insert(message_model.clone()).exec(data).await.unwrap(); + + SeenMessages::insert(seen_messages::server::ActiveModel { + message_id: ActiveValue::Set(inserted_message.last_insert_id), + seen_id: message_model.message_sender_id + }).exec(data).await.unwrap(); + } - - let users = - RetrieveConversations::retrieve_associated_users(user.clone(), data, condition) - .await; - - let messages = RetrieveConversations::retrieve_messages( - &conversations + + async fn insert_seen(data: &DatabaseConnection, message_model: Vec, user_id: i32) { + let existing_ids = SeenMessages::find() + .filter(seen_messages::server::Column::MessageId.is_in(message_model.clone())) + .filter(seen_messages::server::Column::SeenId.eq(user_id)) + .all(data) + .await + .unwrap(); + + let existing_ids: Vec = existing_ids.iter().map(|row| row.message_id).collect(); + + let new_ids: Vec = message_model .iter() - .map(|conversation| conversation.conversation_id) - .collect(), - data, - ) - .await; - - let seen_messages = RetrieveConversations::retrieve_seen(&messages, data).await; - - let vec_merged_conversation = conversations - .iter() - .map(|conversation| { - let conversation_id = conversation.conversation_id; - let conversation_users = users - .iter() - .filter(|user| user.conversation_id == conversation_id) - .collect_vec(); - - let merged_messages: Vec = messages + .filter(|&message_id| !existing_ids.contains(message_id)) + .copied() + .collect(); + + if !new_ids.is_empty() { + let insert_data: Vec = new_ids .iter() - .filter(|message| message.message_conversation_id == conversation_id) - .map(|messages| { - let seen_status = seen_messages - .iter() - .filter(|seen_messages| { - seen_messages.message_id.unwrap() == messages.message_id - }) - .cloned() - .collect_vec(); - - MergedMessages { - message_conversation_id: messages.message_conversation_id, - message_id: messages.message_id, - message_body: messages.message_body.clone(), - message_image: messages.message_image.clone(), - message_sender_id: messages.message_sender_id, - seen_status, - created_at: messages.message_created_at.to_string(), - first_name: messages.first_name.clone(), - last_name: messages.last_name.clone(), - } + .map(|&message_id| seen_messages::server::ActiveModel { + message_id: ActiveValue::Set(message_id), + seen_id: ActiveValue::Set(user_id), }) .collect(); - - let (last_name, first_name) = conversation_users - .iter() - .find(|&users| *users.user_ids.first().unwrap() != user.id) - .map(|user| (user.last_name.clone(), user.first_name.clone())) + SeenMessages::insert_many(insert_data) + .exec(data) + .await .unwrap(); - - let conversation_messages = merged_messages - .into_iter() - .filter(|message| message.message_conversation_id == conversation_id) - .collect(); - - MergedConversation { - conversation_id, - conversation: ConversationInner { - user_ids: conversation_users - .iter() - .rev() - .map(|user| *user.user_ids.first().unwrap()) - .collect(), - last_name, - first_name, - name: conversation.name.clone(), - is_group: conversation.is_group, - messages: conversation_messages, - }, - } - }) - .collect(); - - Ok(vec_merged_conversation) - } - }, - ) - .await? -} - -#[server(Logout, "/api", "Url")] -pub async fn logout(cx: Scope) -> Result<(), ServerFnError> { - use actix_identity::Identity; - - leptos_actix::extract(cx, move |user: Option| async { - user.unwrap().logout() - }) - .await -} - -#[server(ConversationAction, "/api", "Url")] -pub async fn conversation_action( - cx: Scope, - other_users: Vec, - is_group: bool, - name: Option, -) -> Result<(), ServerFnError> { - use crate::entities::prelude::*; - use actix_identity::Identity; - use iter_tools::prelude::Itertools; - use sea_orm::prelude::*; - use sea_orm::*; - - #[derive(FromQueryResult, PartialEq, Eq, Hash, Debug)] - struct ExtractedConversation { - conversation_id: i32, - } - - if other_users.len().lt(&2) && is_group { - return Err(ServerFnError::Args("Not Enough Users Added".to_string())); - }; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - let other_users = other_users.clone(); - let name = name.clone(); - async move { - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let data = &data.lock().await.connection; - - let mut existing_conversation = UserConversation::find() - .select_only() - .column(user_conversation::server::Column::ConversationId) - .column(user_conversation::server::Column::UserIds) - .column(conversation::server::Column::Name) - .column(conversation::server::Column::Id) - .right_join(Conversation); - - match is_group { - true => { - existing_conversation = existing_conversation - .filter(conversation::server::Column::Name.is_not_null()) - } - false => { - existing_conversation = existing_conversation - .filter(conversation::server::Column::Name.is_null()) } - }; - - let resolved_conversations = existing_conversation - .filter( - Condition::any().add( - user_conversation::server::Column::UserIds - .is_in(other_users.clone()) - .add(user_conversation::server::Column::UserIds.eq(user.id)), - ), - ) - .into_model::() - .all(data) - .await?; - - if resolved_conversations.iter().all_unique() || resolved_conversations.len().eq(&0) - { - match is_group { - false => { - log!("Inserting Conversation"); - let conversation = - Conversation::insert(conversation::server::ActiveModel { - is_group: ActiveValue::Set(0), - name: ActiveValue::Set(None), - ..Default::default() - }) - .exec(data) - .await?; - - for user in [user.id, *other_users.first().unwrap()].iter() { - UserConversation::insert(user_conversation::server::ActiveModel { - user_ids: ActiveValue::Set(*user), - conversation_id: ActiveValue::Set(conversation.last_insert_id), - }) - .exec(data) - .await?; - } - } - true => { - let conversation = - Conversation::insert(conversation::server::ActiveModel { - is_group: ActiveValue::Set(1), - name: ActiveValue::Set(name), - ..Default::default() - }) - .exec(data) - .await?; - - let mut vec_users = Vec::new(); - [vec![user.id], other_users] - .iter() - .flatten() - .for_each(|&user| { - vec_users.push(user_conversation::server::ActiveModel { - user_ids: ActiveValue::Set(user), - conversation_id: ActiveValue::Set( - conversation.last_insert_id, - ), - }) - }); - - UserConversation::insert_many(vec_users).exec(data).await?; - } + } + + async fn delete_conversation(conversation_id: i32, data: &sea_orm::DatabaseConnection, user: UserLogin) { + if let Ok(conversation) = Conversation::find(). + filter(Condition::all() + .add(conversation::server::Column::Id.eq(conversation_id)) + .add(user_conversation::server::Column::UserIds.eq(user.id))) + .reverse_join(UserConversation) + .one(data).await { + conversation.unwrap().delete(data).await.unwrap(); } - Ok(()) - } else { - log!("Existing Conversation Found"); - Ok(()) } - } - }, - ) - .await??; - Ok(()) -} - -#[server(ValidateConversation, "/api", "Url")] -pub async fn validate_conversation( - cx: Scope, - desired_conversation_id: i32, -) -> Result, ServerFnError> { - use crate::entities::prelude::*; - use actix_identity::Identity; - use iter_tools::Itertools; - use sea_orm::prelude::*; - use sea_orm::Condition; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let data = &data.lock().await.connection; - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let user_conversations = - RetrieveConversations::retrieve_user_conversations(&user, data).await; - - if !user_conversations - .iter() - .any(|conversation| conversation.conversation_id == desired_conversation_id) - { - return Err(ServerFnError::ServerError("Access Denied".to_string())); - }; - - let conversations = Conversation::find() - .filter(conversation::server::Column::Id.eq(desired_conversation_id)) - .all(data) - .await?; - - let other_users = RetrieveConversations::retrieve_associated_users( - user, - data, - Condition::any().add( - user_conversation::server::Column::ConversationId - .eq(desired_conversation_id), - ), - ) - .await; - - Ok(conversations - .into_iter() - .map(|conversation| ConversationMeta { - id: conversation.id, - last_message_at: conversation.last_message_at.to_string(), - created_at: conversation.created_at.to_string(), - name: conversation.name, - is_group: conversation.is_group, - count: user_conversations.len(), - other_users: other_users - .iter() - .map(|users| { - ( - format!( - "{} {}", - users.first_name.clone(), - &users.last_name.clone() - ), - users.email.clone(), - *users.user_ids.first().unwrap(), - ) - }) - .sorted() - .unique() - .collect(), - }) - .collect()) - } - }, - ) - .await? -} - -#[server(ViewMessages, "/api", "Url")] -pub async fn view_messages( - cx: Scope, - desired_conversation_id: i32, -) -> Result, ServerFnError> { - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>| { - async move { - let data = &data.lock().await.connection; - let messages = RetrieveConversations::retrieve_messages( - &vec![desired_conversation_id], - data - ) - .await; - - let seen_messages = RetrieveConversations::retrieve_seen(&messages, data).await; - - Ok(messages - .iter() - .map(|message| MergedMessages { - message_conversation_id: message.message_conversation_id, - message_id: message.message_id, - message_body: message.message_body.clone(), - created_at: message.message_created_at.to_string(), - message_sender_id: message.message_sender_id, - message_image: message.message_image.clone(), - seen_status: seen_messages - .clone() - .into_iter() - .filter(|seen_messages| { - seen_messages.message_id.unwrap() == message.message_id - }) - .collect(), - first_name: message.first_name.clone(), - last_name: message.last_name.clone(), - }) - .collect::>()) - } - }, - ) - .await? -} - -#[server(AssociatedConversation, "/api", "Url")] -pub async fn associated_conversation(cx: Scope, other_user: i32) -> Result { - use actix_identity::Identity; - use sea_orm::*; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let data = &data.lock().await.connection; - - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let condition = Condition::all() - .add( - Condition::any() - .add(user_conversation::server::Column::UserIds.eq(other_user)) - .add(user_conversation::server::Column::UserIds.eq(user.id)), - ) - .add(conversation::server::Column::IsGroup.eq(0)); - - let conversations = - RetrieveConversations::retrieve_associated_users(user.clone(), data, condition) - .await; - - let user_conversation = conversations - .iter() - .filter(|conversations| { - *conversations.user_ids.first().unwrap() == user.clone().id - }) - .collect::>(); - - Ok(conversations - .iter() - .find_map(|conversations| { - if *conversations.user_ids.first().unwrap() != user.id - && user_conversation.iter().any(|user_conversation| { - user_conversation.conversation_id == conversations.conversation_id - }) - { - Some(conversations.conversation_id) - } else { - None - } - }) - .unwrap()) - } - }, - ) - .await - .unwrap() -} - -#[server(HandleMessageInput, "/api", "Url")] -pub async fn handle_message_input( - cx: Scope, - conversation_id: i32, - body: Option, - image: Option>, -) -> Result, ServerFnError> { - use crate::entities::message; - use actix_identity::Identity; - use image::io::Reader as ImageReader; - - if body.is_none() && image.is_none() { - return Err(server_fn::ServerFnError::MissingArg(String::from( - "Body Missing", - ))); - } - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - let body = body.clone(); - let image = image.clone(); - async move { - let data = &data.lock().await.connection; - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let mut image_location: Option = Default::default(); - - if let Some(image_vec) = image { - let current_time = std::time::UNIX_EPOCH.elapsed()?.as_secs().to_string(); - - if tokio::fs::metadata("./upload").await.is_err() { - tokio::fs::create_dir_all("./upload").await?; - }; - - let kind = infer::get(&image_vec).expect("file type is known"); - let image = if !kind.mime_type().eq("image/png") { - let image = ImageReader::new(std::io::Cursor::new(image_vec)) - .with_guessed_format()? - .decode()?; - - turbojpeg::compress_image( - &image.into_rgba8(), - 50, - turbojpeg::Subsamp::Sub2x2, - )? - .to_vec() - } else { - image_vec - }; - tokio::fs::write("./upload/".to_string() + ¤t_time + ".png", image) - .await?; - image_location = Some("/upload/".to_string() + ¤t_time + ".png") - }; - - AppendDatabase::insert_messages( - data, - message::server::ActiveModel { - message_body: sea_orm::ActiveValue::Set(body), - message_sender_id: sea_orm::ActiveValue::Set(user.id), - message_image: sea_orm::ActiveValue::Set(image_location.clone()), - message_conversation_id: sea_orm::ActiveValue::Set(conversation_id), - ..Default::default() - }, - ) - .await; - - Ok(image_location) - } - }, - ) - .await? -} - -#[server(FindImage, "/api", "Url")] -pub async fn find_image(cx: Scope, image_path: String) -> Result { - Ok( - match tokio::fs::metadata( - std::env::current_dir() - .unwrap() - .join(format!("upload/{}", image_path.split('/').last().unwrap())), - ) - .await - { - Ok(_) => ImageAvailability::Found, - Err(_) => ImageAvailability::Missing, - }, - ) -} - -#[server(HandleSeen, "/api", "Url")] -pub async fn handle_seen(cx: Scope, conversation_id: i32) -> Result<(), ServerFnError> { - use actix_identity::Identity; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let data = &data.lock().await.connection; - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let messages: Vec = - RetrieveConversations::retrieve_messages(&vec![conversation_id], data) - .await - .iter() - .map(|messages| messages.message_id) - .collect(); - log!("MESSAGES {messages:?}"); - AppendDatabase::insert_seen(data, messages, user.id).await; - Ok(()) - } - }, - ) - .await? -} - -#[server(DeleteConversation, "/api", "Url")] -pub async fn delete_conversations(cx: Scope, conversation_id: i32) -> Result<(), ServerFnError> { - use actix_identity::Identity; - - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let data = &data.lock().await.connection; - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - AppendDatabase::delete_conversation(conversation_id, data, user).await; - Ok(()) - } - }, - ) - .await? -} - -#[server(GetUser, "/api", "Url")] -pub async fn get_user(cx: Scope) -> Result { - use actix_identity::Identity; - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - async move { - let data = &data.lock().await.connection; - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - Ok(UserLogin::retrieve_user(user, data).await) - } - }, - ) - .await? -} - -#[server(UploadImage, "/api", "Url")] -pub async fn upload_user_info( - cx: Scope, - image: Option>, - first_name: Option, - last_name: Option, -) -> Result<(), ServerFnError> { - use actix_identity::Identity; - use image::io::Reader as ImageReader; - use validator::Validate; - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>, - user: Option| { - let image = image.clone(); - let first_name = first_name.clone(); - let last_name = last_name.clone(); - async move { - let data = &data.lock().await.connection; - let user = match UserLogin::evaluate_user(user) { - Ok(val) => val, - Err(e) => return Err(e), - }; - - let mut validation_vec = Vec::new(); - - [ - ("first name".to_string(), first_name.clone()), - ("last_name".to_string(), last_name.clone()), - ] - .into_iter() - .for_each(|(entry, name)| { - if let Some(name) = name { - let schema = crate::app::NameSchema { entry: name }; - match schema.validate() { - Ok(_) => (), - Err(e) => validation_vec.push((entry, e)), - } + + async fn modify(user: UserLogin, image: Option, data: &sea_orm::DatabaseConnection, first_name: Option, last_name: Option) { + let mut user_model: users::server::ActiveModel = Users::find_by_id(user.id).one(data).await.unwrap().unwrap().into(); + if let Some(image_path) = image { + user_model.image = Set(Some(image_path)); } - }); - - if validation_vec.len().gt(&0) { - let mut validation_string = String::new(); - for error in validation_vec { - validation_string.push_str( - &(format!( - "Entry {} failed to register with error {}\n", - error.0, error.1 - )), - ) + + if let Some(first_name) = first_name { + user_model.first_name = Set(first_name); } - return Err(ServerFnError::Args(format!( - "Error occured while validating fields: - {validation_string}" - ))); - } - - if let Some(image) = image { - let kind = infer::get(&image).expect("file type is known"); - if kind.mime_type() != "image/jpeg" && kind.mime_type() != "image/png" { - return Err(ServerFnError::Args(format!("Incorrect Mime Type {}", kind))); - }; - let current_time = std::time::UNIX_EPOCH.elapsed()?.as_secs().to_string(); - - if tokio::fs::metadata("./images").await.is_err() { - tokio::fs::create_dir_all("./images").await?; - }; - - let image_path = "images/".to_string() + ¤t_time + ".png"; - let image = if !kind.mime_type().eq("image/png") { - let image = ImageReader::new(std::io::Cursor::new(image)) - .with_guessed_format()? - .decode()?; - - turbojpeg::compress_image( - &image.into_rgba8(), - 50, - turbojpeg::Subsamp::Sub2x2, - )? - .to_vec() - } else { - image - }; - tokio::fs::write(&image_path, image).await?; - - AppendDatabase::modify( - user, - Some(image_path), - data, - first_name.clone(), - last_name.clone(), - ) - .await; - } else { - AppendDatabase::modify(user, None, data, first_name.clone(), last_name.clone()) - .await; - } - - Ok(()) - } - }, - ) - .await? -} - -#[server(GetIcon, "/api", "Url")] -pub async fn get_icon(cx: Scope, id: i32) -> Result>, ServerFnError> { - use tokio::io::AsyncReadExt; - leptos_actix::extract( - cx, - move |data: actix_web::web::Data>| { - async move { - let data = &data.lock().await.connection; - let image = RetrieveConversations::retrieve_images(id, data).await; - if let Some(image) = image { - let path = std::env::current_dir() - .unwrap_or_default() - .join(image); - - if let Ok(mut file) = tokio::fs::File::open(path).await { - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).await.unwrap(); - Some(buffer) - } else { - None - } - } else { - None + + if let Some(last_name) = last_name { + user_model.last_name = Set(last_name); + } + + Users::update(user_model).exec(data).await.unwrap(); } - } - }, - ) - .await -} - -#[server(GetImage, "/api", "Url")] -pub async fn get_image(cx: Scope, path: String) -> Result>, ServerFnError> { - use tokio::io::AsyncReadExt; - let mut path = path; - path.remove(0); - let path = std::env::current_dir().unwrap().join(path); - - let mut buffer = Vec::new(); - if let Ok(mut file) = tokio::fs::File::open(path).await { - file.read_to_end(&mut buffer).await?; - Ok(Some(buffer)) - } else { - Ok(None) + + } } } - -#[server(CreateGroupConversation, "/api", "Url")] -pub async fn create_group_conversations( - cx: Scope, - other_users: String, - is_group: bool, - name: Option, -) -> Result<(), ServerFnError> { - let other_users_vec: Vec = other_users - .split(',') - .map(|user_ids| user_ids.parse::().expect("Invalid user selection")) - .collect(); - conversation_action(cx, other_users_vec, is_group, name).await -} diff --git a/src/server_function/routes/mod.rs b/src/server_function/routes/mod.rs new file mode 100644 index 0000000..c0fbb5e --- /dev/null +++ b/src/server_function/routes/mod.rs @@ -0,0 +1,1155 @@ +use iter_tools::Itertools; +use leptos::{log, server, server_fn, Scope, ServerFnError}; + +use crate::{ + app::{EmailSchema, FormValidation, PhoneSchema, VerificationValidation, VerifyPassword}, + entities::{conversation, user_conversation}, + server_function::{ + AppendDatabase, ConversationInner, ConversationMeta, MergedConversation, + RetrieveConversations, UserLogin, + }, +}; + +use super::{ImageAvailability, MergedMessages, UserModel}; + +#[server(SignUp, "/api", "Url")] +pub async fn sign_up( + cx: Scope, + form: crate::app::SignupSchema, +) -> Result { + use super::super::entities::{prelude::*, *}; + use argon2::{ + password_hash::{rand_core::OsRng, PasswordHasher, SaltString}, + Argon2, + }; + use rand::Rng; + use sea_orm::*; + + let struct_vector: Vec> = vec![ + Box::new(form.first_name.clone()), + Box::new(form.last_name.clone()), + Box::new(form.email.clone()), + Box::new(form.password.clone()), + Box::new(form.phone_number.clone()), + ]; + + if struct_vector.iter().any(|item| item.validate().is_err()) { + Ok(crate::app::FormValidation::Error) + } else { + // if there is an email entry, then return + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>| { + let form = form.clone(); + let entry = form.email.entry.clone(); + + async move { + if Users::find() + .filter(users::server::Column::Email.eq(entry.clone())) + .one(&data.lock().await.connection) + .await? + .is_some() + { + Ok(crate::app::FormValidation::EmailPresent) + } else if Users::find() + .filter( + users::server::Column::PhoneNumber.eq(form + .phone_number + .entry + .replace('+', "") + .parse::()?), + ) + .one(&data.lock().await.connection) + .await? + .is_some() + { + Ok(super::super::app::FormValidation::PhonePresent) + } else { + let special_characters = "!@#$%^&*"; + + // Generate a random 15-letter string with lowercase, uppercase, and special characters + let mut rng = rand::thread_rng(); + let random_string: String = (0..15) + .map(|_| { + let charset: Vec = match rng.gen_range(0..3) { + 0 => (b'a'..=b'z').collect(), + 1 => (b'A'..=b'Z').collect(), + _ => special_characters.bytes().collect(), + }; + char::from(charset[rng.gen_range(0..charset.len())]) + }) + .collect(); + + let new_user = temp_users::server::ActiveModel { + first_name: ActiveValue::Set(form.first_name.entry.clone()), + last_name: ActiveValue::Set(form.last_name.entry), + email: ActiveValue::Set(form.email.entry.clone()), + phone_number: ActiveValue::Set( + form.phone_number + .entry + .chars() + .filter(|c| c.is_ascii_digit()) + .collect::() + .parse::()?, + ), + password: ActiveValue::Set({ + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + argon2 + .hash_password(form.password.entry.as_bytes(), &salt) + .unwrap() + .to_string() + }), + verification: { + // Define the special characters to include in the random string + + ActiveValue::Set(random_string.clone()) + }, + time: ActiveValue::Set(chrono::Utc::now()), + ..Default::default() + }; + if TempUsers::insert(new_user) + .exec(&data.lock().await.connection) + .await + .is_ok() + { + Ok(super::super::app::FormValidation::Success { + random_string: Some(random_string), + }) + } else { + Ok(super::super::app::FormValidation::Error) + } + } + } + }, + ) + .await? + } +} + +#[server(Validate, "/api", "Url")] +pub async fn cred_validation( + cx: Scope, + email: Option, + phone_number: Option, +) -> Result { + use super::super::entities::{prelude::*, *}; + use sea_orm::*; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>| { + let email = email.clone(); + let phone_number = phone_number.clone(); + async move { + let db = &data.lock().await.connection; + if let Some(email) = email { + if TempUsers::find() + .filter(temp_users::server::Column::Email.eq(email.entry.clone())) + .one(db) + .await? + .is_some() + || Users::find() + .filter(users::server::Column::Email.eq(email.entry)) + .one(db) + .await? + .is_some() + { + Ok(FormValidation::EmailPresent) + } else { + Ok(FormValidation::Success { + random_string: None, + }) + } + } else if TempUsers::find() + .filter( + temp_users::server::Column::PhoneNumber.eq(phone_number + .clone() + .unwrap() + .entry + .replace('+', "") + .parse::() + .unwrap()), + ) + .one(db) + .await? + .is_some() + || Users::find() + .filter( + users::server::Column::PhoneNumber.eq(phone_number + .clone() + .unwrap() + .entry + .parse::() + .unwrap()), + ) + .one(db) + .await? + .is_some() + { + Ok(FormValidation::PhonePresent) + } else { + Ok(FormValidation::Success { + random_string: None, + }) + } + } + }, + ) + .await? +} + +#[server(VerifyEmail, "/api", "Url")] +pub async fn send_verification_email( + email: String, + first_name: String, + random_string: String, +) -> Result { + match crate::emailing::email_client::send_email(email, first_name, random_string) { + Ok(_) => Ok(String::from("Successful Signup")), + Err(e) => Ok(format!("Error at sending verification email: {e}")), + } +} + +#[server(ConfirmSubscription, "/api", "Url")] +pub async fn confirm_subscription( + cx: Scope, + email: String, + input: String, +) -> Result { + use super::super::entities::{prelude::*, *}; + use sea_orm::*; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>| { + let email = email.clone(); + let input = input.clone(); + async move { + let db = &data.lock().await.connection; + if let Ok(user) = TempUsers::find() + .filter(temp_users::server::Column::Email.eq(email)) + .one(&db.clone()) + .await + .map_err(|_| VerificationValidation::EmailNotPresent) + { + let user = user.unwrap(); + if user.verification.trim().replace('"', "") == input.trim().replace('"', "") { + let registered_user = users::server::ActiveModel { + first_name: ActiveValue::Set(user.first_name.clone()), + last_name: ActiveValue::Set(user.last_name.clone()), + email: ActiveValue::Set(user.email.clone()), + phone_number: ActiveValue::Set(user.phone_number), + password: ActiveValue::Set(user.password.clone()), + ..Default::default() + }; + + println!("Inserting into db"); + if Users::insert(registered_user.clone()) + .exec(&db.clone()) + .await + .is_ok() + { + Ok(VerificationValidation::Success) + } else { + Ok(super::super::app::VerificationValidation::ServerError) + } + } else { + Ok(VerificationValidation::IncorrectValidationCode) + } + } else { + Ok(VerificationValidation::ServerError) + } + } + }, + ) + .await? +} + +#[server(Login, "/api", "Url")] +pub async fn login( + cx: Scope, + email: String, + password: String, +) -> Result { + use super::super::entities::{prelude::*, *}; + use actix_identity::Identity; + use actix_web::HttpMessage; + use actix_web::HttpRequest; + use argon2::{ + password_hash::{PasswordHash, PasswordVerifier}, + Argon2, + }; + use sea_orm::*; + + log!("retrieving request"); + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + request: HttpRequest| { + log!("retrieved request"); + let email = email.clone(); + let password = password.clone(); + async move { + let db = &data.lock().await.connection; + if let Some(user) = Users::find() + .filter(users::server::Column::Email.eq(email.clone())) + .one(db) + .await? + { + let parsed_hash = PasswordHash::new(&user.password).unwrap(); + match Argon2::default() + .verify_password(password.as_bytes(), &parsed_hash) + .is_ok() + { + true => { + Identity::login( + &request.extensions(), + serde_json::to_string_pretty(&UserLogin { + id: user.id, + email: user.email.clone(), + first_name: user.first_name.clone(), + last_name: user.last_name.clone(), + })?, + ) + .unwrap(); + + Ok(VerifyPassword::Success(UserLogin { + id: user.id, + email: user.email, + first_name: user.first_name, + last_name: user.last_name, + })) + } + false => Ok(VerifyPassword::IncorrectCredentials), + } + } else { + Ok(VerifyPassword::IncorrectCredentials) + } + } + }, + ) + .await? +} + +#[server(LoginStatus, "/api", "Url")] +pub async fn login_status(cx: Scope) -> Result { + use actix_identity::Identity; + + leptos_actix::extract(cx, move |user: Option| async { + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + Ok(user) + }) + .await? +} + +#[server(Redirect, "/api", "Url")] +pub async fn redirect(cx: Scope) -> Result { + use actix_identity::Identity; + leptos_actix::extract( + cx, + move |user: Option| async move { user.is_none() }, + ) + .await +} + +// #[cfg(feature = "ssr")] +#[server(GetUsers, "/api", "Url")] +pub async fn get_users(cx: Scope) -> Result, ServerFnError> { + use super::super::entities::prelude::*; + use super::super::entities::users; + use sea_orm::*; + + Ok(leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let data = &data.lock().await.connection; + Ok(Users::find() + .order_by_asc(users::server::Column::Id) + .filter(users::server::Column::Id.ne(user.id)) + .all(data) + .await?) + } + }, + ) + .await?? + .into_iter() + .map_into() + .rev() + .collect()) +} + +#[server(GetConversations, "/api", "Url")] +pub async fn get_conversations(cx: Scope) -> Result, ServerFnError> { + use actix_identity::Identity; + use sea_orm::*; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let data = &data.lock().await.connection; + let conversations = + RetrieveConversations::retrieve_user_conversations(&user, data).await; + + let mut condition = Condition::any(); + for conversation in &conversations { + condition = condition.add( + user_conversation::server::Column::ConversationId + .eq(conversation.conversation_id), + ); + } + + let users = + RetrieveConversations::retrieve_associated_users(user.clone(), data, condition) + .await; + + let messages = RetrieveConversations::retrieve_messages( + &conversations + .iter() + .map(|conversation| conversation.conversation_id) + .collect(), + data, + ) + .await; + + let seen_messages = RetrieveConversations::retrieve_seen(&messages, data).await; + + let vec_merged_conversation = conversations + .iter() + .map(|conversation| { + let conversation_id = conversation.conversation_id; + let conversation_users = users + .iter() + .filter(|user| user.conversation_id == conversation_id) + .collect_vec(); + + let merged_messages: Vec = messages + .iter() + .filter(|message| message.message_conversation_id == conversation_id) + .map(|messages| { + let seen_status = seen_messages + .iter() + .filter(|seen_messages| { + seen_messages.message_id.unwrap() == messages.message_id + }) + .cloned() + .collect_vec(); + + MergedMessages { + message_conversation_id: messages.message_conversation_id, + message_id: messages.message_id, + message_body: messages.message_body.clone(), + message_image: messages.message_image.clone(), + message_sender_id: messages.message_sender_id, + seen_status, + created_at: messages.message_created_at.to_string(), + first_name: messages.first_name.clone(), + last_name: messages.last_name.clone(), + } + }) + .collect(); + + let (last_name, first_name) = conversation_users + .iter() + .find(|&users| *users.user_ids.first().unwrap() != user.id) + .map(|user| (user.last_name.clone(), user.first_name.clone())) + .unwrap(); + + let conversation_messages = merged_messages + .into_iter() + .filter(|message| message.message_conversation_id == conversation_id) + .collect(); + + MergedConversation { + conversation_id, + conversation: ConversationInner { + user_ids: conversation_users + .iter() + .rev() + .map(|user| *user.user_ids.first().unwrap()) + .collect(), + last_name, + first_name, + name: conversation.name.clone(), + is_group: conversation.is_group, + messages: conversation_messages, + }, + } + }) + .collect(); + + Ok(vec_merged_conversation) + } + }, + ) + .await? +} + +#[server(Logout, "/api", "Url")] +pub async fn logout(cx: Scope) -> Result<(), ServerFnError> { + use actix_identity::Identity; + + leptos_actix::extract(cx, move |user: Option| async { + user.unwrap().logout() + }) + .await +} + +#[server(ConversationAction, "/api", "Url")] +pub async fn conversation_action( + cx: Scope, + other_users: Vec, + is_group: bool, + name: Option, +) -> Result<(), ServerFnError> { + use crate::entities::prelude::*; + use actix_identity::Identity; + use iter_tools::prelude::Itertools; + use sea_orm::prelude::*; + use sea_orm::*; + + #[derive(FromQueryResult, PartialEq, Eq, Hash, Debug)] + struct ExtractedConversation { + conversation_id: i32, + } + + if other_users.len().lt(&2) && is_group { + return Err(ServerFnError::Args("Not Enough Users Added".to_string())); + }; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + let other_users = other_users.clone(); + let name = name.clone(); + async move { + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let data = &data.lock().await.connection; + + let mut existing_conversation = UserConversation::find() + .select_only() + .column(user_conversation::server::Column::ConversationId) + .column(user_conversation::server::Column::UserIds) + .column(conversation::server::Column::Name) + .column(conversation::server::Column::Id) + .right_join(Conversation); + + match is_group { + true => { + existing_conversation = existing_conversation + .filter(conversation::server::Column::Name.is_not_null()) + } + false => { + existing_conversation = existing_conversation + .filter(conversation::server::Column::Name.is_null()) + } + }; + + let resolved_conversations = existing_conversation + .filter( + Condition::any().add( + user_conversation::server::Column::UserIds + .is_in(other_users.clone()) + .add(user_conversation::server::Column::UserIds.eq(user.id)), + ), + ) + .into_model::() + .all(data) + .await?; + + if resolved_conversations.iter().all_unique() || resolved_conversations.len().eq(&0) + { + match is_group { + false => { + log!("Inserting Conversation"); + let conversation = + Conversation::insert(conversation::server::ActiveModel { + is_group: ActiveValue::Set(0), + name: ActiveValue::Set(None), + ..Default::default() + }) + .exec(data) + .await?; + + for user in [user.id, *other_users.first().unwrap()].iter() { + UserConversation::insert(user_conversation::server::ActiveModel { + user_ids: ActiveValue::Set(*user), + conversation_id: ActiveValue::Set(conversation.last_insert_id), + }) + .exec(data) + .await?; + } + } + true => { + let conversation = + Conversation::insert(conversation::server::ActiveModel { + is_group: ActiveValue::Set(1), + name: ActiveValue::Set(name), + ..Default::default() + }) + .exec(data) + .await?; + + let mut vec_users = Vec::new(); + [vec![user.id], other_users] + .iter() + .flatten() + .for_each(|&user| { + vec_users.push(user_conversation::server::ActiveModel { + user_ids: ActiveValue::Set(user), + conversation_id: ActiveValue::Set( + conversation.last_insert_id, + ), + }) + }); + + UserConversation::insert_many(vec_users).exec(data).await?; + } + } + Ok(()) + } else { + log!("Existing Conversation Found"); + Ok(()) + } + } + }, + ) + .await??; + Ok(()) +} + +#[server(ValidateConversation, "/api", "Url")] +pub async fn validate_conversation( + cx: Scope, + desired_conversation_id: i32, +) -> Result, ServerFnError> { + use crate::entities::prelude::*; + use actix_identity::Identity; + use iter_tools::Itertools; + use sea_orm::prelude::*; + use sea_orm::Condition; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let data = &data.lock().await.connection; + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let user_conversations = + RetrieveConversations::retrieve_user_conversations(&user, data).await; + + if !user_conversations + .iter() + .any(|conversation| conversation.conversation_id == desired_conversation_id) + { + return Err(ServerFnError::ServerError("Access Denied".to_string())); + }; + + let conversations = Conversation::find() + .filter(conversation::server::Column::Id.eq(desired_conversation_id)) + .all(data) + .await?; + + let other_users = RetrieveConversations::retrieve_associated_users( + user, + data, + Condition::any().add( + user_conversation::server::Column::ConversationId + .eq(desired_conversation_id), + ), + ) + .await; + + Ok(conversations + .into_iter() + .map(|conversation| ConversationMeta { + id: conversation.id, + last_message_at: conversation.last_message_at.to_string(), + created_at: conversation.created_at.to_string(), + name: conversation.name, + is_group: conversation.is_group, + count: user_conversations.len(), + other_users: other_users + .iter() + .map(|users| { + ( + format!( + "{} {}", + users.first_name.clone(), + &users.last_name.clone() + ), + users.email.clone(), + *users.user_ids.first().unwrap(), + ) + }) + .sorted() + .unique() + .collect(), + }) + .collect()) + } + }, + ) + .await? +} + +#[server(ViewMessages, "/api", "Url")] +pub async fn view_messages( + cx: Scope, + desired_conversation_id: i32, +) -> Result, ServerFnError> { + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>| { + async move { + let data = &data.lock().await.connection; + let messages = RetrieveConversations::retrieve_messages( + &vec![desired_conversation_id], + data + ) + .await; + + let seen_messages = RetrieveConversations::retrieve_seen(&messages, data).await; + + Ok(messages + .iter() + .map(|message| MergedMessages { + message_conversation_id: message.message_conversation_id, + message_id: message.message_id, + message_body: message.message_body.clone(), + created_at: message.message_created_at.to_string(), + message_sender_id: message.message_sender_id, + message_image: message.message_image.clone(), + seen_status: seen_messages + .clone() + .into_iter() + .filter(|seen_messages| { + seen_messages.message_id.unwrap() == message.message_id + }) + .collect(), + first_name: message.first_name.clone(), + last_name: message.last_name.clone(), + }) + .collect::>()) + } + }, + ) + .await? +} + +#[server(AssociatedConversation, "/api", "Url")] +pub async fn associated_conversation(cx: Scope, other_user: i32) -> Result { + use actix_identity::Identity; + use sea_orm::*; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let data = &data.lock().await.connection; + + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let condition = Condition::all() + .add( + Condition::any() + .add(user_conversation::server::Column::UserIds.eq(other_user)) + .add(user_conversation::server::Column::UserIds.eq(user.id)), + ) + .add(conversation::server::Column::IsGroup.eq(0)); + + let conversations = + RetrieveConversations::retrieve_associated_users(user.clone(), data, condition) + .await; + + let user_conversation = conversations + .iter() + .filter(|conversations| { + *conversations.user_ids.first().unwrap() == user.clone().id + }) + .collect::>(); + + Ok(conversations + .iter() + .find_map(|conversations| { + if *conversations.user_ids.first().unwrap() != user.id + && user_conversation.iter().any(|user_conversation| { + user_conversation.conversation_id == conversations.conversation_id + }) + { + Some(conversations.conversation_id) + } else { + None + } + }) + .unwrap()) + } + }, + ) + .await + .unwrap() +} + +#[server(HandleMessageInput, "/api", "Url")] +pub async fn handle_message_input( + cx: Scope, + conversation_id: i32, + body: Option, + image: Option>, +) -> Result, ServerFnError> { + use crate::entities::message; + use actix_identity::Identity; + use image::io::Reader as ImageReader; + + if body.is_none() && image.is_none() { + return Err(server_fn::ServerFnError::MissingArg(String::from( + "Body Missing", + ))); + } + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + let body = body.clone(); + let image = image.clone(); + async move { + let data = &data.lock().await.connection; + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let mut image_location: Option = Default::default(); + + if let Some(image_vec) = image { + let current_time = std::time::UNIX_EPOCH.elapsed()?.as_secs().to_string(); + + if tokio::fs::metadata("./upload").await.is_err() { + tokio::fs::create_dir_all("./upload").await?; + }; + + let kind = infer::get(&image_vec).expect("file type is known"); + let image = if !kind.mime_type().eq("image/png") { + let image = ImageReader::new(std::io::Cursor::new(image_vec)) + .with_guessed_format()? + .decode()?; + + turbojpeg::compress_image( + &image.into_rgba8(), + 50, + turbojpeg::Subsamp::Sub2x2, + )? + .to_vec() + } else { + image_vec + }; + tokio::fs::write("./upload/".to_string() + ¤t_time + ".png", image) + .await?; + image_location = Some("/upload/".to_string() + ¤t_time + ".png") + }; + + AppendDatabase::insert_messages( + data, + message::server::ActiveModel { + message_body: sea_orm::ActiveValue::Set(body), + message_sender_id: sea_orm::ActiveValue::Set(user.id), + message_image: sea_orm::ActiveValue::Set(image_location.clone()), + message_conversation_id: sea_orm::ActiveValue::Set(conversation_id), + ..Default::default() + }, + ) + .await; + + Ok(image_location) + } + }, + ) + .await? +} + +#[server(FindImage, "/api", "Url")] +pub async fn find_image(cx: Scope, image_path: String) -> Result { + Ok( + match tokio::fs::metadata( + std::env::current_dir() + .unwrap() + .join(format!("upload/{}", image_path.split('/').last().unwrap())), + ) + .await + { + Ok(_) => ImageAvailability::Found, + Err(_) => ImageAvailability::Missing, + }, + ) +} + +#[server(HandleSeen, "/api", "Url")] +pub async fn handle_seen(cx: Scope, conversation_id: i32) -> Result<(), ServerFnError> { + use actix_identity::Identity; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let data = &data.lock().await.connection; + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let messages: Vec = + RetrieveConversations::retrieve_messages(&vec![conversation_id], data) + .await + .iter() + .map(|messages| messages.message_id) + .collect(); + log!("MESSAGES {messages:?}"); + AppendDatabase::insert_seen(data, messages, user.id).await; + Ok(()) + } + }, + ) + .await? +} + +#[server(DeleteConversation, "/api", "Url")] +pub async fn delete_conversations(cx: Scope, conversation_id: i32) -> Result<(), ServerFnError> { + use actix_identity::Identity; + + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let data = &data.lock().await.connection; + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + AppendDatabase::delete_conversation(conversation_id, data, user).await; + Ok(()) + } + }, + ) + .await? +} + +#[server(GetUser, "/api", "Url")] +pub async fn get_user(cx: Scope) -> Result { + use actix_identity::Identity; + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + async move { + let data = &data.lock().await.connection; + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + Ok(UserLogin::retrieve_user(user, data).await) + } + }, + ) + .await? +} + +#[server(UploadImage, "/api", "Url")] +pub async fn upload_user_info( + cx: Scope, + image: Option>, + first_name: Option, + last_name: Option, +) -> Result<(), ServerFnError> { + use actix_identity::Identity; + use image::io::Reader as ImageReader; + use validator::Validate; + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>, + user: Option| { + let image = image.clone(); + let first_name = first_name.clone(); + let last_name = last_name.clone(); + async move { + let data = &data.lock().await.connection; + let user = match UserLogin::evaluate_user(user) { + Ok(val) => val, + Err(e) => return Err(e), + }; + + let mut validation_vec = Vec::new(); + + [ + ("first name".to_string(), first_name.clone()), + ("last_name".to_string(), last_name.clone()), + ] + .into_iter() + .for_each(|(entry, name)| { + if let Some(name) = name { + let schema = crate::app::NameSchema { entry: name }; + match schema.validate() { + Ok(_) => (), + Err(e) => validation_vec.push((entry, e)), + } + } + }); + + if validation_vec.len().gt(&0) { + let mut validation_string = String::new(); + for error in validation_vec { + validation_string.push_str( + &(format!( + "Entry {} failed to register with error {}\n", + error.0, error.1 + )), + ) + } + return Err(ServerFnError::Args(format!( + "Error occured while validating fields: + {validation_string}" + ))); + } + + if let Some(image) = image { + let kind = infer::get(&image).expect("file type is known"); + if kind.mime_type() != "image/jpeg" && kind.mime_type() != "image/png" { + return Err(ServerFnError::Args(format!("Incorrect Mime Type {}", kind))); + }; + let current_time = std::time::UNIX_EPOCH.elapsed()?.as_secs().to_string(); + + if tokio::fs::metadata("./images").await.is_err() { + tokio::fs::create_dir_all("./images").await?; + }; + + let image_path = "images/".to_string() + ¤t_time + ".png"; + let image = if !kind.mime_type().eq("image/png") { + let image = ImageReader::new(std::io::Cursor::new(image)) + .with_guessed_format()? + .decode()?; + + turbojpeg::compress_image( + &image.into_rgba8(), + 50, + turbojpeg::Subsamp::Sub2x2, + )? + .to_vec() + } else { + image + }; + tokio::fs::write(&image_path, image).await?; + + AppendDatabase::modify( + user, + Some(image_path), + data, + first_name.clone(), + last_name.clone(), + ) + .await; + } else { + AppendDatabase::modify(user, None, data, first_name.clone(), last_name.clone()) + .await; + } + + Ok(()) + } + }, + ) + .await? +} + +#[server(GetIcon, "/api", "Url")] +pub async fn get_icon(cx: Scope, id: i32) -> Result>, ServerFnError> { + use tokio::io::AsyncReadExt; + leptos_actix::extract( + cx, + move |data: actix_web::web::Data>| { + async move { + let data = &data.lock().await.connection; + let image = RetrieveConversations::retrieve_images(id, data).await; + if let Some(image) = image { + let path = std::env::current_dir() + .unwrap_or_default() + .join(image); + + if let Ok(mut file) = tokio::fs::File::open(path).await { + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).await.unwrap(); + Some(buffer) + } else { + None + } + } else { + None + } + } + }, + ) + .await +} + +#[server(GetImage, "/api", "Url")] +pub async fn get_image(cx: Scope, path: String) -> Result>, ServerFnError> { + use tokio::io::AsyncReadExt; + let mut path = path; + path.remove(0); + let path = std::env::current_dir().unwrap().join(path); + + let mut buffer = Vec::new(); + if let Ok(mut file) = tokio::fs::File::open(path).await { + file.read_to_end(&mut buffer).await?; + Ok(Some(buffer)) + } else { + Ok(None) + } +} + +#[server(CreateGroupConversation, "/api", "Url")] +pub async fn create_group_conversations( + cx: Scope, + other_users: String, + is_group: bool, + name: Option, +) -> Result<(), ServerFnError> { + let other_users_vec: Vec = other_users + .split(',') + .map(|user_ids| user_ids.parse::().expect("Invalid user selection")) + .collect(); + conversation_action(cx, other_users_vec, is_group, name).await +} diff --git a/templates/email.html b/templates/email.html new file mode 100644 index 0000000..e69de29