From c125cbb7a5729db892a39b4967ee386f58567650 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marek=20Dlouh=C3=BD?= <marek.dlouhy@plus4u.net>
Date: Sun, 14 Jan 2024 14:44:51 +0100
Subject: [PATCH] feat: user registration

---
 .../src/handlers/templates/register.rs        | 57 +++++++++++-
 cumhub_backend/src/init.rs                    |  3 +-
 .../src/repositories/user/repository.rs       | 12 +++
 .../templates/register_page/page_content.html | 87 ++++++++++++++-----
 4 files changed, 134 insertions(+), 25 deletions(-)

diff --git a/cumhub_backend/src/handlers/templates/register.rs b/cumhub_backend/src/handlers/templates/register.rs
index 8759969..5dab0f3 100644
--- a/cumhub_backend/src/handlers/templates/register.rs
+++ b/cumhub_backend/src/handlers/templates/register.rs
@@ -1,10 +1,16 @@
 use std::ops::Deref;
-use actix_web::{get, HttpRequest, Result as ActixResult, HttpResponse, error::ErrorInternalServerError};
-use actix_web::web::Data;
+use actix_web::{get, HttpRequest, Result as ActixResult, HttpResponse, post, error::ErrorInternalServerError};
+use actix_web::web::{Data, Form, Json};
 use askama::Template;
+use diesel::IntoSql;
 use jwt_simple::algorithms::HS256Key;
+use jwt_simple::prelude::Serialize;
+use serde::Deserialize;
 use crate::{templates::RegisterTemplate};
-use crate::utils::auth::AuthHandler;
+use crate::repositories::user::models::NewUser;
+use crate::repositories::user::repository::{PgUserRepository, UserRepository};
+use crate::repositories::video::repository::PgVideoRepository;
+use crate::utils::auth::{AuthHandler, hash_password};
 
 #[get("/register")]
 pub async fn get_register(
@@ -14,6 +20,8 @@ pub async fn get_register(
     let cookies = data.cookies().unwrap().clone();
     let auth = AuthHandler::new(cookies, key.deref());
     let _ = auth.get_token_from_cookie();
+    let user = auth.get_user_info_from_token();
+
 
     let template = RegisterTemplate {
         already_logged_in: auth.user_logged_in(),
@@ -22,4 +30,47 @@ pub async fn get_register(
     let body = template.render().map_err(ErrorInternalServerError)?;
 
     Ok(HttpResponse::Ok().content_type("text/html").body(body))
+}
+
+
+#[derive(Serialize, Deserialize)]
+struct RegisterParams {
+    username: String,
+    email: String,
+    profile_pic: String,
+    password: String
+}
+#[post("/register")]
+pub async fn post_register(
+    user: Form<RegisterParams>,
+    data: Data<PgUserRepository>
+) -> ActixResult<HttpResponse> {
+    let form = user.into_inner();
+
+    let email_taken = data.get_user_by_email(form.email.clone());
+    if email_taken.is_ok() {
+        return Ok(HttpResponse::BadRequest().body("Email already taken".to_string()));
+    }
+
+    let username_taken = data.get_user_by_username(form.username.clone());
+    if username_taken.is_ok() {
+        return Ok(HttpResponse::BadRequest().body("Username already taken".to_string()));
+    }
+
+    let hash_result = hash_password(form.password.clone());
+    let hash = match hash_result {
+        Ok(hash) => hash,
+        Err(_) => return Ok(HttpResponse::InternalServerError().body("Error hashing password".to_string()))
+    };
+
+    let user = NewUser {
+        username: form.username,
+        email: form.email,
+        password_hash: hash.unprotected_as_encoded().to_string(),
+        profile_pic_url: form.profile_pic
+    };
+
+    let user = data.create_user(user).unwrap();
+
+    Ok(HttpResponse::SeeOther().append_header(("Location", "/")).content_type("text/html").finish())
 }
\ No newline at end of file
diff --git a/cumhub_backend/src/init.rs b/cumhub_backend/src/init.rs
index 3189cd7..f78b182 100644
--- a/cumhub_backend/src/init.rs
+++ b/cumhub_backend/src/init.rs
@@ -14,7 +14,7 @@ use crate::handlers::templates::index::filter_index_videos;
 use crate::handlers::templates::login::{get_login, logout_user, post_login};
 use crate::handlers::templates::premium::{get_premium, post_premium_subscribe};
 use crate::handlers::templates::profile::filter_videos;
-use crate::handlers::templates::register::get_register;
+use crate::handlers::templates::register::{get_register, post_register};
 use crate::handlers::templates::subscribed::{
     get_subscribed, post_search_channels, post_search_subscribed_videos,
 };
@@ -80,6 +80,7 @@ pub fn configure_webapp(_config: &mut ServiceConfig) {
         .service(get_account_settings)
         .service(upload_video)
         .service(get_register)
+        .service(post_register)
         .service(get_subscribed)
         .service(post_search_channels)
         .service(get_channel)
diff --git a/cumhub_backend/src/repositories/user/repository.rs b/cumhub_backend/src/repositories/user/repository.rs
index 3e50ade..4506745 100644
--- a/cumhub_backend/src/repositories/user/repository.rs
+++ b/cumhub_backend/src/repositories/user/repository.rs
@@ -16,6 +16,7 @@ use crate::schema::user::{deleted_at, email, id, username};
 pub trait UserRepository {
     fn get_user(&self, user_id: Uuid) -> Result<User, DatabaseError>;
     fn get_user_by_username(&self, username_filter: String) -> Result<User, DatabaseError>;
+    fn get_user_by_email(&self, email_filter: String) -> Result<User, DatabaseError>;
     fn create_user(&self, new_user: NewUser) -> Result<User, DatabaseError>;
     fn edit_user(&self, user_id: Uuid, patch_user: PartialUser) -> Result<User, DatabaseError>;
     fn delete_user(&self, user_id: Uuid) -> Result<(), DatabaseError>;
@@ -55,6 +56,17 @@ impl UserRepository for PgUserRepository {
         Ok(user)
     }
 
+    fn get_user_by_email(&self, email_filter: String) -> Result<User, DatabaseError> {
+        let mut conn = self.pg_pool.get()?;
+        let user = schema::user::dsl::user
+            .filter(email.eq(email_filter))
+            .filter(deleted_at.is_null())
+            .select(User::as_select())
+            .first(conn.deref_mut())?;
+
+        Ok(user)
+    }
+
     fn create_user(&self, new_user: NewUser) -> Result<User, DatabaseError> {
         let mut conn = self.pg_pool.get()?;
         let user = diesel::insert_into(schema::user::table)
diff --git a/cumhub_backend/templates/register_page/page_content.html b/cumhub_backend/templates/register_page/page_content.html
index 3f205b2..66de9ef 100644
--- a/cumhub_backend/templates/register_page/page_content.html
+++ b/cumhub_backend/templates/register_page/page_content.html
@@ -4,24 +4,21 @@
     {% include
         "./common/user_already_logged.html" %}
     {% else %} 
-    <p class="text-white text-3xl">Welcome to Cumhub!</p>
+    <p class="text-3xl text-white">Welcome to Cumhub!</p>
 
-    <form hx-on:submit="{
-        event.preventDefault();
-        document.cookie = 'token=token; path=/';
-        window.location.href = '/';
-    }" class="w-2/5 h-auto py-10 bg-gray-800 rounded-xl flex flex-col items-center">
+    <form class="flex flex-col items-center w-2/5 h-auto py-10 bg-gray-800 rounded-xl"
+          hx-post="/register" hx-push-url="true">
         <!--REGISTER CONTAINER-->
-        <div id="register_container" class="w-full h-auto pb-10 bg-gray-800 rounded-xl flex flex-col items-center">
+        <div id="register_container" class="flex flex-col items-center w-full h-auto pb-10 bg-gray-800 rounded-xl">
             
-            <a href="/" class="mb-4 w-full flex justify-start pl-8">
-                <span class="material-symbols-outlined text-gray-400">
+            <a href="/" class="flex justify-start w-full pl-8 mb-4">
+                <span class="text-gray-400 material-symbols-outlined">
                     arrow_back_ios
                 </span>
             </a>
             
             <div id="username_div" class="w-[90%] flex flex-col gap-2 mb-8">
-                <label for="username_input" class="text-white text-xl">
+                <label for="username_input" class="text-xl text-white">
                     Username:
                 </label>
                 <div id="username_input_div"
@@ -29,23 +26,71 @@
                     hx-on:click="{
                         document.getElementById('username_input').focus();
                     }">
-                    <span class="material-symbols-outlined text-white">
+                    <span class="text-white material-symbols-outlined">
                         person
                     </span>
-                    <input id="username_input" hx-on:focus="{
+                    <input name="username" id="username_input" hx-on:focus="{
                             document.getElementById('username_input_div').classList.remove('border-white'); 
                             document.getElementById('username_input_div').classList.add('border-cyan-300');
                         }" hx-on:blur="{
                             document.getElementById('username_input_div').classList.remove('border-cyan-300');      
                             document.getElementById('username_input_div').classList.add('border-white');                   
-                        }" placeholder="Username or email"
+                        }" placeholder="Username"
                         class="w-full text-white h-full bg-transparent !outline-none" />
                 </div>
+            </div>
+            <div id="email_div" class="w-[90%] flex flex-col gap-2 mb-8">
+                <label for="email_input" class="text-xl text-white">
+                    Email:
+                </label>
+                <div id="email_input_div"
+                    class="px-2 email_input_div border-white border-2 flex h-[4.5rem] w-full items-center gap-2 rounded-lg"
+                    hx-on:click="{
+                        document.getElementById('email_input').focus();
+                    }">
+                    <span class="text-white material-symbols-outlined">
+                        person
+                    </span>
+                    <input id="email_input"
+                           name="email"
+                           hx-on:focus="{
+                            document.getElementById('email_input_div').classList.remove('border-white'); 
+                            document.getElementById('email_input_div').classList.add('border-cyan-300');
+                        }" hx-on:blur="{
+                            document.getElementById('email_input_div').classList.remove('border-cyan-300');      
+                            document.getElementById('email_input_div').classList.add('border-white');                   
+                        }" placeholder="Email"
+                        class="w-full text-white h-full bg-transparent !outline-none" />
+                </div>
+            </div>
 
+            <div id="profile_pic_div" class="w-[90%] flex flex-col gap-2 mb-8">
+                <label for="profile_pic_input" class="text-xl text-white">
+                    Profile picture url:
+                </label>
+                <div id="profile_pic_input_div"
+                     class="px-2 profile_pic_input_div border-white border-2 flex h-[4.5rem] w-full items-center gap-2 rounded-lg"
+                     hx-on:click="{
+                        document.getElementById('profile_pic_input').focus();
+                    }">
+                    <span class="text-white material-symbols-outlined">
+                        person
+                    </span>
+                    <input id="profile_pic_input"
+                           name="profile_pic"
+                           hx-on:focus="{
+                            document.getElementById('profile_pic_input_div').classList.remove('border-white');
+                            document.getElementById('profile_pic_input_div').classList.add('border-cyan-300');
+                        }" hx-on:blur="{
+                            document.getElementById('profile_pic_input_div').classList.remove('border-cyan-300');
+                            document.getElementById('profile_pic_input_div').classList.add('border-white');
+                        }" placeholder="profile_pic"
+                           class="w-full text-white h-full bg-transparent !outline-none" />
+                </div>
             </div>
 
             <div id="password_div" class="w-[90%] flex flex-col gap-2">
-                <label for="password_input" class="text-white text-xl">
+                <label for="password_input" class="text-xl text-white">
                     Password:
                 </label>
                 <div id="password_input_div"
@@ -53,10 +98,10 @@
                     hx-on:click="{
                         document.getElementById('password_input').focus();
                     }">
-                    <span class="material-symbols-outlined text-white">
+                    <span class="text-white material-symbols-outlined">
                         lock
                     </span>
-                    <input type="password" id="password_input" hx-on:focus="{
+                    <input name="password" type="password" id="password_input" hx-on:focus="{
                             document.getElementById('password_input_div').classList.remove('border-white'); 
                             document.getElementById('password_input_div').classList.add('border-cyan-300');
                         }" hx-on:blur="{
@@ -64,11 +109,11 @@
                             document.getElementById('password_input_div').classList.add('border-white');                   
                         }" placeholder="Password" class="w-full text-white h-full bg-transparent !outline-none" />
                 </div>
-                <span id="password_error" class="invisible text-red-800 text-sm">Password must have one lowercase letter, one capital letter, one number and has at least 8 characters!</span>
+                <span id="password_error" class="invisible text-sm text-red-800">Password must have one lowercase letter, one capital letter, one number and has at least 8 characters!</span>
             </div>
 
             <div id="password_confirm_div" class="w-[90%] flex flex-col gap-2">
-                <label for="password_confirm_input" class="text-white text-xl">
+                <label for="password_confirm_input" class="text-xl text-white">
                     Password Again:
                 </label>
                 <div id="password_confirm_input_div"
@@ -76,7 +121,7 @@
                     hx-on:click="{
                         document.getElementById('password_confirm_input').focus();
                     }">
-                    <span class="material-symbols-outlined text-white">
+                    <span class="text-white material-symbols-outlined">
                         lock
                     </span>
                     <input type="password" id="password_confirm_input" hx-on:focus="{
@@ -87,14 +132,14 @@
                             document.getElementById('password_confirm_input_div').classList.add('border-white');                   
                         }" placeholder="Password" class="w-full text-white h-full bg-transparent !outline-none" />
                 </div>
-                <span id="password_confirm_error" class="invisible text-red-800 text-sm">Passwords must match!</span>
+                <span id="password_confirm_error" class="invisible text-sm text-red-800">Passwords must match!</span>
             </div>
         
             <button type="submit"
                 class="w-2/5 h-[4rem] mt-4 rounded-md mb-4 text-lg bg-blue-700 hover:bg-blue-800 text-white">
                 Submit
             </button>
-            <div class="w-full flex justify-center gap-4 px-10 mt-10">
+            <div class="flex justify-center w-full gap-4 px-10 mt-10">
                 <p class="text-white">
                     Already have an account?
                 </p>
-- 
GitLab