diff --git a/.env b/.env old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/Cargo.lock b/Cargo.lock old mode 100644 new mode 100755 diff --git a/Cargo.toml b/Cargo.toml old mode 100644 new mode 100755 diff --git a/css/base.css b/css/base.css new file mode 100755 index 0000000..16897ad --- /dev/null +++ b/css/base.css @@ -0,0 +1,36 @@ +body { + background-color: #92a8d1; + font-family: Arial, Helvetica, sans-serif; + height: 100vh; +} +@media(max-width: 500px) { + body { + padding: 1px; + display: grid; + grid-template-columns: 1fr; + } + } +@media(min-width: 501px) and (max-width: 550px) { + body { + padding: 1px; + display: grid; + grid-template-columns: 1fr 5fr 1fr; + } + .mainContainer {grid-column-start: 2;} +} +@media(min-width: 551px) and (max-width: 1000px) { + body { + padding: 1px; + display: grid; + grid-template-columns: 1fr 3fr 1fr; + } + .mainContainer {grid-column-start: 2;} +} +@media(min-width: 1001px) { + body { + padding: 1px; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + } + .mainContainer {grid-column-start: 2;} +} \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100755 index 0000000..21d3939 --- /dev/null +++ b/css/main.css @@ -0,0 +1,37 @@ +.itemContainer { + background: #034f84; + margin: 0.3rem; +} +.itemContainer:hover { + background: #034f99; +} +.itemContainer p { + color: white; + display: inline-block; + margin: 0.5rem; + margin-right: 0.4rem; + margin-left: 0.4rem; +} +.actionButton { + display: inline-block; + float: right; + background: #f7786b; + border: none; + padding: 0.5rem; + padding-left: 2rem; + padding-right: 2rem; + color: white; +} +.actionButton:hover { + background: #f7686b; + color: black; +} +.inputContainer { + background: #034f84; + margin: 0.3rem; + margin-top: 2rem; +} +.inputContainer input { + display: inline-block; + margin: 0.3rem + } \ No newline at end of file diff --git a/diesel.toml b/diesel.toml old mode 100644 new mode 100755 diff --git a/docker-compose.yml b/docker-compose.yml old mode 100644 new mode 100755 diff --git a/javascript/login.js b/javascript/login.js new file mode 100755 index 0000000..6a01cd9 --- /dev/null +++ b/javascript/login.js @@ -0,0 +1,36 @@ +const loginButton = document.getElementById('loginButton'); +const username = document.getElementById( + 'defaultLoginFormUsername'); +const password = document.getElementById( + 'defaultLoginFormPassword'); +const message = document.getElementById("loginMessage"); + + +loginButton.addEventListener("click", () => { + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/api/v1/auth/login", true); + xhr.setRequestHeader("Content-Type", + "application/json"); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + let token = xhr.getResponseHeader("token"); + localStorage.setItem("user-token", token); + console.log("status 200: " + document.location.origin); + window.location.replace( + document.location.origin); + } else { + message.innerText = + "login failed please try again"; + } + } + }; + let data = JSON.stringify({ + "username": username.value, + "password": password.value + }); + + xhr.send(data); + message.innerText = "logging in"; + +}) diff --git a/javascript/main.js b/javascript/main.js new file mode 100755 index 0000000..c3d2e49 --- /dev/null +++ b/javascript/main.js @@ -0,0 +1,35 @@ +if (localStorage.getItem("user-token") == null) { + window.location.replace(document.location.origin + "/login"); +} else { + getArticles(); +} + +function apiCall(url, method) { + let xhr = new XMLHttpRequest(); + xhr.withCredentials = true; + + xhr.addEventListener("readystatechange", function () { + if (this.readyState === this.DONE) { + if (this.status === 401) { + window.location.replace(document.location.origin + "/login/"); + } else { + runRenderProcess(JSON.parse(this.responseText)); + localStorage.setItem("item-cache-date", new Date()); + localStorage.setItem("item-cache-data", this.responseText); + } + } + }); + xhr.open(method, "/api/v1" + url); + xhr.setRequestHeader("content-type", "application/json"); + xhr.setRequestHeader("user-token", localStorage.getItem("user-token")); + return xhr; +} + +function runRenderProcess(params) { + document.getElementById("mainContainer").innerHtml = params; +} + +function getArticles() { + let call = apiCall("/article/get", "GET"); + call.send(); +} diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql old mode 100644 new mode 100755 diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql old mode 100644 new mode 100755 diff --git a/migrations/2022-11-21-174138_create_users/down.sql b/migrations/2022-11-21-174138_create_users/down.sql old mode 100644 new mode 100755 diff --git a/migrations/2022-11-21-174138_create_users/up.sql b/migrations/2022-11-21-174138_create_users/up.sql old mode 100644 new mode 100755 diff --git a/migrations/2022-11-26-102303_feed/down.sql b/migrations/2022-11-26-102303_feed/down.sql new file mode 100755 index 0000000..d6f1fd3 --- /dev/null +++ b/migrations/2022-11-26-102303_feed/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE feed_item diff --git a/migrations/2022-11-26-102303_feed/up.sql b/migrations/2022-11-26-102303_feed/up.sql new file mode 100755 index 0000000..2c52a35 --- /dev/null +++ b/migrations/2022-11-26-102303_feed/up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here +CREATE TABLE feed ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) DEFERRABLE INITIALLY DEFERRED, + title VARCHAR NOT NULL, + url VARCHAR NOT NULL, + UNIQUE (url) +) diff --git a/migrations/2022-11-26-102327_feed_item/down.sql b/migrations/2022-11-26-102327_feed_item/down.sql new file mode 100755 index 0000000..b8503e0 --- /dev/null +++ b/migrations/2022-11-26-102327_feed_item/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +CREATE TABLE feed_item { + +} diff --git a/migrations/2022-11-26-102327_feed_item/up.sql b/migrations/2022-11-26-102327_feed_item/up.sql new file mode 100755 index 0000000..dccdc73 --- /dev/null +++ b/migrations/2022-11-26-102327_feed_item/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE feed_item ( + id SERIAL PRIMARY KEY, + feed_id INTEGER NOT NULL REFERENCES feed(id) DEFERRABLE INITIALLY DEFERRED, + content TEXT NOT NULL, + read BOOLEAN NOT NULL DEFAULT FALSE +) diff --git a/src/auth/jwt.rs b/src/auth/jwt.rs new file mode 100755 index 0000000..5a8bef1 --- /dev/null +++ b/src/auth/jwt.rs @@ -0,0 +1,104 @@ +extern crate hmac; +extern crate jwt; +extern crate sha2; + +use std::collections::BTreeMap; + +use actix_web::HttpRequest; +use hmac::{Hmac, Mac}; +use jwt::{Header, SignWithKey, Token, VerifyWithKey}; +use sha2::Sha256; + +pub struct JwtToken { + pub user_id: i32, + pub body: String, +} + +type HmacSha256 = Hmac; + +impl JwtToken { + pub fn encode(user_id: i32) -> String { + let key: HmacSha256 = HmacSha256::new_from_slice(b"secret").unwrap(); + let mut claims = BTreeMap::new(); + claims.insert("user_id", user_id); + claims.sign_with_key(&key).unwrap() + } + + pub fn decode(encoded_token: String) -> Result { + let key: HmacSha256 = HmacSha256::new_from_slice(b"secret").unwrap(); + let token_str: &str = encoded_token.as_str(); + let token: Result, jwt::Verified>, jwt::Error> = + VerifyWithKey::verify_with_key(token_str, &key); + + match token { + Ok(token) => { + let _header = token.header(); + let claims = token.claims(); + Ok(JwtToken { + user_id: claims["user_id"], + body: encoded_token, + }) + } + Err(_err) => Err("could not decode token"), + } + } + + pub fn decode_from_request(request: HttpRequest) -> Result { + match request.headers().get("user-token") { + Some(token) => JwtToken::decode(String::from(token.to_str().unwrap())), + None => Err("There is no token"), + } + } +} + +#[cfg(test)] +mod jwt_test { + use actix_web::{http::header, test}; + + use super::JwtToken; + + #[test] + async fn encode_decode() { + let encoded_token: String = JwtToken::encode(32); + let decoded_token: JwtToken = JwtToken::decode(encoded_token).unwrap(); + assert_eq!(32, decoded_token.user_id); + } + + #[test] + async fn decode_incorrect_token() { + let encoded_token: String = String::from("test"); + + match JwtToken::decode(encoded_token) { + Err(message) => assert_eq!(message, "could not decode token"), + _ => panic!("Incorrect token should not be able to decode."), + } + } + + #[actix_web::test] + async fn decode_from_request_with_correct_token() { + let encoded_token: String = JwtToken::encode(32); + let request = test::TestRequest::default() + .insert_header(header::ContentType::json()) + .insert_header(("user-token", encoded_token)) + .to_http_request(); + let out_come = JwtToken::decode_from_request(request); + + match out_come { + Ok(token) => assert_eq!(32, token.user_id), + _ => panic!("Token is not returned with it should be."), + } + } + + #[actix_web::test] + async fn decode_from_request_with_no_token() { + let request = test::TestRequest::default() + .insert_header(("test", "test")) + .to_http_request(); + let out_come = JwtToken::decode_from_request(request); + + match out_come { + Err(message) => assert_eq!("There is no token", message), + _ => panic!("Token should not be returned when it is not present in the header."), + } + } +} diff --git a/src/auth/mod.rs b/src/auth/mod.rs new file mode 100755 index 0000000..1c4fb3b --- /dev/null +++ b/src/auth/mod.rs @@ -0,0 +1,55 @@ +use actix_web::dev::ServiceRequest; +pub mod jwt; +pub mod processes; +use crate::auth::processes::check_password; +use crate::auth::processes::extract_header_token; + +pub fn process_token(request: &ServiceRequest) -> Result { + match extract_header_token(request) { + Ok(token) => check_password(token), + Err(message) => Err(message), + } +} + +#[cfg(test)] +mod mod_test { + + use actix_web::test::TestRequest; + + use super::{jwt::JwtToken, process_token}; + + #[test] + fn process_token_test() { + let token = JwtToken::encode(32); + let request = TestRequest::delete() + .insert_header(("user-token", token)) + .to_srv_request(); + + match process_token(&request) { + Ok(message) => assert_eq!("passed", message), + Err(_) => panic!("process token failed"), + } + } + + #[actix_web::test] + async fn process_token_not_existing() { + let request = TestRequest::default().to_srv_request(); + + match process_token(&request) { + Err(error) => assert_eq!("there is no token", error), + _ => panic!("Not existing token should not be processes"), + } + } + + #[actix_web::test] + async fn process_wrong_token() { + let request = TestRequest::default() + .insert_header(("user-token", "bla")) + .to_srv_request(); + + match process_token(&request) { + Err(error) => assert_eq!("could not decode token", error), + _ => panic!("Not existing token should not be processes"), + } + } +} diff --git a/src/auth/processes.rs b/src/auth/processes.rs new file mode 100755 index 0000000..de31962 --- /dev/null +++ b/src/auth/processes.rs @@ -0,0 +1,75 @@ + +use super::jwt; +use actix_web::dev::ServiceRequest; + +pub fn check_password(password: String) -> Result { + match jwt::JwtToken::decode(password) { + Ok(_token) => Ok(String::from("passed")), + Err(message) => Err(message), + } +} + +pub fn extract_header_token(request: &ServiceRequest) -> Result { + match request.headers().get("user-token") { + Some(token) => match token.to_str() { + Ok(processed_password) => Ok(String::from(processed_password)), + Err(_processed_password) => Err("there was an error processing token"), + }, + None => Err("there is no token"), + } +} + +#[cfg(test)] +mod processes_test { + use actix_web::test::TestRequest; + + use crate::auth::jwt::JwtToken; + + use super::check_password; + + #[test] + fn check_correct_password() { + let password_string: String = JwtToken::encode(32); + + let result = check_password(password_string); + + match result { + Ok(check) => assert_eq!("passed", check), + _ => panic!("Check correct password failed."), + } + } + + #[test] + fn incorrect_check_password() { + let password: String = String::from("test"); + + match check_password(password) { + Err(message) => assert_eq!("could not decode token", message), + _ => panic!("check password should not be able to be decoded"), + } + } + + #[test] + fn successful_extract_header_token() { + let request = TestRequest::default() + .insert_header(("user-token", "token")) + .to_srv_request(); + + match super::extract_header_token(&request) { + Ok(processed_password) => assert_eq!("token", processed_password), + _ => panic!("failed extract_header_token"), + } + } + + #[test] + fn failed_extract_header_token() { + let request = TestRequest::default() + .insert_header(("wrong", "bla")) + .to_srv_request(); + + match super::extract_header_token(&request) { + Err(processed_password) => assert_eq!("there is no token", processed_password), + _ => panic!("Extract header token should fail when not provided."), + } + } +} diff --git a/src/database.rs b/src/database.rs new file mode 100755 index 0000000..e5b2924 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,12 @@ +use diesel::pg::PgConnection; +use diesel::prelude::*; +use dotenv::dotenv; +use std::env; + +pub fn establish_connection() -> PgConnection { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + PgConnection::establish(&database_url) + .unwrap_or_else(|_| panic!("Error connecting to database {}", database_url)) +} diff --git a/src/json_serialization/articles.rs b/src/json_serialization/articles.rs new file mode 100755 index 0000000..e69de29 diff --git a/src/json_serialization/login.rs b/src/json_serialization/login.rs new file mode 100755 index 0000000..9043303 --- /dev/null +++ b/src/json_serialization/login.rs @@ -0,0 +1,7 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct Login { + pub username: String, + pub password: String, +} diff --git a/src/json_serialization/mod.rs b/src/json_serialization/mod.rs new file mode 100755 index 0000000..4b458a4 --- /dev/null +++ b/src/json_serialization/mod.rs @@ -0,0 +1,2 @@ +pub mod login; +pub mod new_user; diff --git a/src/json_serialization/new_user.rs b/src/json_serialization/new_user.rs new file mode 100755 index 0000000..f80e3b0 --- /dev/null +++ b/src/json_serialization/new_user.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct NewUserSchema { + pub name: String, + pub email: String, + pub password: String, +} diff --git a/src/main.rs b/src/main.rs old mode 100644 new mode 100755 index 9cce696..e58411b --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,7 @@ extern crate dotenv; use actix_service::Service; use actix_web::{App, HttpResponse, HttpServer}; -use env_logger; use futures::future::{ok, Either}; -use log; mod auth; mod database; mod json_serialization; @@ -31,6 +29,7 @@ async fn main() -> std::io::Result<()> { Err(_message) => passed = false, } } else { + log::warn!("No auth check done."); passed = true; } diff --git a/src/models/feed/feed.rs b/src/models/feed/feed.rs new file mode 100755 index 0000000..eb1144b --- /dev/null +++ b/src/models/feed/feed.rs @@ -0,0 +1,13 @@ +use super::super::feed; +use super::super::user::user::User; +use diesel::{Associations, Identifiable, Queryable}; + +#[derive(Queryable, Identifiable, Associations)] +#[diesel(belongs_to(User))] +#[diesel(table_name=feed)] +pub struct Feed { + pub id: i32, + pub user_id: i32, + pub title: String, + pub url: String, +} diff --git a/src/models/feed/mod.rs b/src/models/feed/mod.rs new file mode 100755 index 0000000..7065d2b --- /dev/null +++ b/src/models/feed/mod.rs @@ -0,0 +1 @@ +pub mod feed; diff --git a/src/models/feed_item/feed_item.rs b/src/models/feed_item/feed_item.rs new file mode 100755 index 0000000..139597f --- /dev/null +++ b/src/models/feed_item/feed_item.rs @@ -0,0 +1,2 @@ + + diff --git a/src/models/feed_item/mod.rs b/src/models/feed_item/mod.rs new file mode 100755 index 0000000..ea937a6 --- /dev/null +++ b/src/models/feed_item/mod.rs @@ -0,0 +1 @@ +mod feed_item; diff --git a/src/models/mod.rs b/src/models/mod.rs old mode 100644 new mode 100755 diff --git a/src/models/user/mod.rs b/src/models/user/mod.rs old mode 100644 new mode 100755 diff --git a/src/models/user/new_user.rs b/src/models/user/new_user.rs old mode 100644 new mode 100755 diff --git a/src/models/user/user.rs b/src/models/user/user.rs old mode 100644 new mode 100755 diff --git a/src/reader/feeds.rs b/src/reader/feeds.rs old mode 100644 new mode 100755 diff --git a/src/reader/get.rs b/src/reader/get.rs new file mode 100755 index 0000000..7275ead --- /dev/null +++ b/src/reader/get.rs @@ -0,0 +1,9 @@ +use actix_web::{HttpRequest, HttpResponse}; +use crate::auth::jwt::JwtToken; + + +pub async fn get(req: HttpRequest) -> HttpResponse { + let token: JwtToken = JwtToken::decode_from_request(req).unwrap(); + + todo!(); +} diff --git a/src/reader/mod.rs b/src/reader/mod.rs old mode 100644 new mode 100755 index abf705f..075a477 --- a/src/reader/mod.rs +++ b/src/reader/mod.rs @@ -1 +1,16 @@ +use actix_web::web; + +use crate::views::path::Path; pub mod feeds; +mod get; + +pub fn feed_factory(app: &mut web::ServiceConfig) { + let base_path: Path = Path { + prefix: String::from("/article"), + backend: true, + }; + app.route( + &base_path.define(String::from("/get")), + web::get().to(get::get), + ); +} diff --git a/src/schema.rs b/src/schema.rs old mode 100644 new mode 100755 diff --git a/src/views/app/content_loader.rs b/src/views/app/content_loader.rs new file mode 100755 index 0000000..f60e73f --- /dev/null +++ b/src/views/app/content_loader.rs @@ -0,0 +1,23 @@ +use std::fs; + +pub fn read_file(file_path: &str) -> String { + let data: String = fs::read_to_string(file_path) + .expect(format!("Unable to read file {}", file_path).as_str()); + return data; +} + +pub fn add_component(component_tag: String, html_data: String) -> String { + let css_tag: String = component_tag.to_uppercase() + &String::from("_CSS"); + let html_tag: String = component_tag.to_uppercase() + &String::from("_HTML"); + let css_path = String::from("./templates/components/") + + &component_tag.to_lowercase() + + &String::from(".css"); + let css_loaded = read_file(&css_path); + let html_path = String::from("./templates/components/") + + &component_tag.to_lowercase() + + &String::from(".html"); + let html_loaded = read_file(&html_path); + let html_data = html_data.replace(html_tag.as_str(), &html_loaded); + let html_data = html_data.replace(css_tag.as_str(),&css_loaded); + return html_data +} \ No newline at end of file diff --git a/src/views/app/login.rs b/src/views/app/login.rs new file mode 100755 index 0000000..d09273d --- /dev/null +++ b/src/views/app/login.rs @@ -0,0 +1,17 @@ +use super::content_loader::read_file; +use actix_web::HttpResponse; + +pub async fn login() -> HttpResponse { + let mut html_data = read_file(&String::from("./templates/login.html")); + let javascript_data = read_file(&String::from("./javascript/login.js")); + let css_data = read_file(&String::from("./css/main.css")); + let base_css_data = read_file(&String::from("./css/base.css")); + + html_data = html_data.replace("{{JAVASCRIPT}}", &javascript_data); + html_data = html_data.replace("{{CSS}}", &css_data); + html_data = html_data.replace("{{BASE_CSS}}", &base_css_data); + + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html_data) +} diff --git a/src/views/app/logout.rs b/src/views/app/logout.rs new file mode 100755 index 0000000..e689d53 --- /dev/null +++ b/src/views/app/logout.rs @@ -0,0 +1,15 @@ +use actix_web::HttpResponse; + +pub async fn logout() -> HttpResponse { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body( + "\ + \ + + ", + ) +} diff --git a/src/views/app/mod.rs b/src/views/app/mod.rs new file mode 100755 index 0000000..e3b5cfe --- /dev/null +++ b/src/views/app/mod.rs @@ -0,0 +1,35 @@ +use actix_web::web; +mod content_loader; +mod login; +mod logout; +mod reader; +use super::path::Path; + +/// This function adds the app views to the web server serving HTML. +/// +/// # Arguments +/// * (&mut web::ServiceConfig): reference to the app for configuration +/// +/// # Returns +/// None +pub fn app_factory(app: &mut web::ServiceConfig) { + // define the path struct + let base_path: Path = Path { + prefix: String::from("/"), + backend: false, + }; + // define the routes for the app + + app.route( + &base_path.define(String::from("")), + web::get().to(reader::reader), + ); + app.route( + &base_path.define(String::from("login")), + web::get().to(login::login), + ); + app.route( + &base_path.define(String::from("logout")), + web::get().to(logout::logout), + ); +} diff --git a/src/views/app/reader.rs b/src/views/app/reader.rs new file mode 100755 index 0000000..fd3feac --- /dev/null +++ b/src/views/app/reader.rs @@ -0,0 +1,18 @@ +use super::content_loader::{add_component, read_file}; +use actix_web::HttpResponse; + +pub async fn reader() -> HttpResponse { + let mut html_data = read_file("./templates/reader.html"); + let javascript = read_file("./javascript/main.js"); + let css = read_file("./css/main.css"); + let base_css = read_file("./css/base.css"); + + html_data = html_data.replace("JAVASCRIPT", &javascript); + html_data = html_data.replace("CSS", &css); + html_data = html_data.replace("BASE_CSS", &base_css); + // html_data = add_component(String::from("header"), html_data); + + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html_data) +} diff --git a/src/views/auth/login.rs b/src/views/auth/login.rs new file mode 100755 index 0000000..9e131e0 --- /dev/null +++ b/src/views/auth/login.rs @@ -0,0 +1,44 @@ +use crate::database::establish_connection; +use crate::diesel; +use crate::json_serialization::login::Login; +use crate::models::user::user::User; +use crate::schema::users; +use crate::{auth::jwt::JwtToken, schema::users::username}; +use actix_web::{web, HttpResponse}; +use diesel::prelude::*; + +pub async fn login(credentials: web::Json) -> HttpResponse { + let username_cred: String = credentials.username.clone(); + let password: String = credentials.password.clone(); + + let mut connection = establish_connection(); + + let users = users::table + .filter(username.eq(username_cred.as_str())) + .load::(&mut connection) + .unwrap(); + + if users.is_empty() { + return HttpResponse::NotFound().await.unwrap(); + } else if users.len() > 1 { + log::error!( + "multiple user have the usernam: {}", + credentials.username.clone() + ); + return HttpResponse::Conflict().await.unwrap(); + } + + let user: &User = &users[0]; + + match user.clone().verify(password) { + true => { + log::info!("verified password successfully"); + let token: String = JwtToken::encode(user.clone().id); + HttpResponse::Ok() + .insert_header(("token", token)) + .await + .unwrap() + } + false => HttpResponse::Unauthorized().await.unwrap(), + } +} diff --git a/src/views/auth/logout.rs b/src/views/auth/logout.rs new file mode 100755 index 0000000..0c8cceb --- /dev/null +++ b/src/views/auth/logout.rs @@ -0,0 +1,3 @@ +pub async fn logout() -> String { + format!("logout view") +} diff --git a/src/views/auth/mod.rs b/src/views/auth/mod.rs new file mode 100755 index 0000000..e5c644a --- /dev/null +++ b/src/views/auth/mod.rs @@ -0,0 +1,23 @@ +use actix_web::web; +use actix_web::web::ServiceConfig; + +use super::path::Path; + +mod login; +mod logout; + +pub fn auth_factory(app: &mut ServiceConfig) { + let base_path: Path = Path { + prefix: String::from("/auth"), + backend: true, + }; + + app.route( + &base_path.define(String::from("/login")), + web::post().to(login::login), + ); + app.route( + &base_path.define(String::from("/logout")), + web::post().to(logout::logout), + ); +} diff --git a/src/views/mod.rs b/src/views/mod.rs old mode 100644 new mode 100755 index 30b4de2..f11b7f2 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -1,10 +1,14 @@ use actix_web::web; + +use crate::reader; +mod app; +mod auth; pub(crate) mod path; mod users; pub fn views_factory(app: &mut web::ServiceConfig) { - // auth::auth_factory(app); - // to_do::item_factory(app); - // app::app_factory(app); + auth::auth_factory(app); + app::app_factory(app); users::user_factory(app); + reader::feed_factory(app); } diff --git a/src/views/path.rs b/src/views/path.rs old mode 100644 new mode 100755 index 0def744..ff10055 --- a/src/views/path.rs +++ b/src/views/path.rs @@ -14,3 +14,4 @@ impl Path { } } } + diff --git a/src/views/users/create.rs b/src/views/users/create.rs new file mode 100755 index 0000000..9c58f6e --- /dev/null +++ b/src/views/users/create.rs @@ -0,0 +1,25 @@ +use crate::database::establish_connection; +use crate::diesel; +use crate::json_serialization::new_user::NewUserSchema; +use crate::models::user::new_user::NewUser; +use crate::schema::users; +use actix_web::{web, HttpResponse}; +use diesel::prelude::*; + +pub async fn create(new_user: web::Json) -> HttpResponse { + let mut connection = establish_connection(); + let name: String = new_user.name.clone(); + let email: String = new_user.email.clone(); + let new_password: String = new_user.password.clone(); + + let new_user = NewUser::new(name, email, new_password); + + let insert_result = diesel::insert_into(users::table) + .values(&new_user) + .execute(&mut connection); + + match insert_result { + Ok(_) => HttpResponse::Created().await.unwrap(), + Err(_) => HttpResponse::Conflict().await.unwrap(), + } +} diff --git a/src/views/users/mod.rs b/src/views/users/mod.rs new file mode 100755 index 0000000..6c5ea36 --- /dev/null +++ b/src/views/users/mod.rs @@ -0,0 +1,15 @@ +use super::path::Path; +use actix_web::web; +mod create; + +pub fn user_factory(app: &mut web::ServiceConfig) { + let base_path: Path = Path { + prefix: String::from("/user"), + backend: true, + }; + + app.route( + &base_path.define(String::from("/create")), + web::post().to(create::create), + ); +} diff --git a/templates/components/header.css b/templates/components/header.css new file mode 100755 index 0000000..42f2061 --- /dev/null +++ b/templates/components/header.css @@ -0,0 +1,12 @@ +.header { + background: #034f84; + margin-bottom: 0.3rem; +} + +.header p { + color: white; + display: inline-block; + margin: 0.5rem; + margin-right: 0.4rem; + margin-left: 0.4rem; +} \ No newline at end of file diff --git a/templates/components/header.html b/templates/components/header.html new file mode 100755 index 0000000..1992601 --- /dev/null +++ b/templates/components/header.html @@ -0,0 +1,4 @@ +
+

complete tasks:

+

pending tasks:

+
\ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100755 index 0000000..422ea97 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,51 @@ + + + + + + + + Login + + + + + +
+

Login

+

+
+
+

+

+ +
+
+ + + + diff --git a/templates/reader.html b/templates/reader.html new file mode 100755 index 0000000..9a9e2b0 --- /dev/null +++ b/templates/reader.html @@ -0,0 +1,26 @@ + + + + + + + + ToDo App + + + + +
+ + +