diff options
Diffstat (limited to 'src/routes')
-rw-r--r-- | src/routes/error.rs | 40 | ||||
-rw-r--r-- | src/routes/home.rs | 21 | ||||
-rw-r--r-- | src/routes/login.rs | 82 | ||||
-rw-r--r-- | src/routes/logout.rs | 11 | ||||
-rw-r--r-- | src/routes/mod.rs | 7 | ||||
-rw-r--r-- | src/routes/signup.rs | 79 | ||||
-rw-r--r-- | src/routes/static.rs | 22 | ||||
-rw-r--r-- | src/routes/users.rs | 16 |
8 files changed, 278 insertions, 0 deletions
diff --git a/src/routes/error.rs b/src/routes/error.rs new file mode 100644 index 0000000..543b8a8 --- /dev/null +++ b/src/routes/error.rs @@ -0,0 +1,40 @@ +use actix_web::{ + body::{BoxBody, EitherBody, MessageBody}, + dev::ServiceResponse, + http::{header, StatusCode}, + middleware::ErrorHandlerResponse, +}; + +use crate::templates; + +pub fn render_404(res: ServiceResponse) -> actix_web::Result<ErrorHandlerResponse<BoxBody>> { + Ok(error_response( + res, + StatusCode::NOT_FOUND, + "The resource you requested can't be found.", + )) +} + +pub fn render_500(res: ServiceResponse) -> actix_web::Result<ErrorHandlerResponse<BoxBody>> { + Ok(error_response( + res, + StatusCode::INTERNAL_SERVER_ERROR, + "Sorry, Something went wrong. This is probably not your fault.", + )) +} + +fn error_response( + mut res: ServiceResponse, + status_code: StatusCode, + message: &str, +) -> ErrorHandlerResponse<BoxBody> { + res.headers_mut().insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()), + ); + ErrorHandlerResponse::Response(res.map_body(|_head, _body| { + EitherBody::right(MessageBody::boxed( + render!(templates::error_html, status_code, message).unwrap(), + )) + })) +} diff --git a/src/routes/home.rs b/src/routes/home.rs new file mode 100644 index 0000000..11d3a72 --- /dev/null +++ b/src/routes/home.rs @@ -0,0 +1,21 @@ +use actix_session::Session; +use actix_web::{get, web, HttpResponse}; + +use crate::templates; +use crate::{Pinussy, Result}; + +#[get("/")] +async fn get(session: Session, state: web::Data<Pinussy>) -> Result<HttpResponse> { + let username: Option<String>; + if let Some(user_id) = session.get::<i32>("user_id")? { + username = Some( + sqlx::query!("select username from users where id = $1", user_id) + .fetch_one(&state.db) + .await? + .username, + ) + } else { + username = None + } + return Ok(HttpResponse::Ok().body(render!(templates::home_html, username).unwrap())); +} diff --git a/src/routes/login.rs b/src/routes/login.rs new file mode 100644 index 0000000..33f7f69 --- /dev/null +++ b/src/routes/login.rs @@ -0,0 +1,82 @@ +use actix_session::Session; +use actix_web::http::header::LOCATION; +use actix_web::{get, post, web, HttpResponse}; +use bcrypt::verify; +use serde::Deserialize; + +use crate::notification::{Kind, Notification}; +use crate::templates; +use crate::Pinussy; +use crate::Result; + +#[get("/login")] +async fn get() -> HttpResponse { + HttpResponse::Ok().body(render!(templates::login_html, None).unwrap()) +} + +#[derive(Deserialize)] +struct LoginForm { + username: String, + password: String, + rememberme: Option<String>, +} + +#[post("/login")] +async fn post( + state: web::Data<Pinussy>, + session: Session, + form: web::Form<LoginForm>, +) -> Result<HttpResponse> { + match sqlx::query!( + "select id, password from users where username = $1", + &form.username + ) + .fetch_one(&state.db) + .await + { + Ok(user) => { + let password_hash: String = user.password; + if verify(&form.password, &password_hash)? { + session.insert("user_id", user.id)?; + return Ok(HttpResponse::SeeOther() + .insert_header((LOCATION, "/")) + .finish()); + } else { + return Ok(HttpResponse::Unauthorized().body( + render!( + templates::login_html, + Some(Notification { + kind: Kind::Error, + message: "that password is incorrect".to_owned() + }) + ) + .unwrap(), + )); + } + } + Err(sqlx::Error::RowNotFound) => { + return Ok(HttpResponse::NotFound().body( + render!( + templates::login_html, + Some(Notification { + kind: Kind::Error, + message: format!("the user \"{}\" does not exist", &form.username) + }) + ) + .unwrap(), + )); + } + Err(_) => { + return Ok(HttpResponse::InternalServerError().body( + render!( + templates::login_html, + Some(Notification { + kind: Kind::Error, + message: "internal server error. please try again later".to_owned() + }) + ) + .unwrap(), + )); + } + } +} diff --git a/src/routes/logout.rs b/src/routes/logout.rs new file mode 100644 index 0000000..712b74c --- /dev/null +++ b/src/routes/logout.rs @@ -0,0 +1,11 @@ +use actix_session::Session; +use actix_web::http::header::LOCATION; +use actix_web::{post, HttpResponse}; + +#[post("/logout")] +async fn post(session: Session) -> HttpResponse { + session.purge(); + HttpResponse::SeeOther() + .insert_header((LOCATION, "/")) + .finish() +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs new file mode 100644 index 0000000..3b73f38 --- /dev/null +++ b/src/routes/mod.rs @@ -0,0 +1,7 @@ +pub mod error; +pub mod home; +pub mod login; +pub mod logout; +pub mod signup; +pub mod r#static; +pub mod users; diff --git a/src/routes/signup.rs b/src/routes/signup.rs new file mode 100644 index 0000000..ae10201 --- /dev/null +++ b/src/routes/signup.rs @@ -0,0 +1,79 @@ +use actix_web::{get, post, web, HttpResponse}; +use serde::Deserialize; + +use crate::notification::{Kind as NotificationKind, Notification}; +use crate::templates; +use crate::Pinussy; +use crate::Result; +use bcrypt::{hash, DEFAULT_COST}; + +#[get("/signup")] +async fn get() -> HttpResponse { + HttpResponse::Ok().body(render!(templates::signup_html, None).unwrap()) +} + +#[derive(Deserialize)] +struct SignupForm { + username: String, + password: String, +} + +#[post("/signup")] +async fn post(state: web::Data<Pinussy>, form: web::Form<SignupForm>) -> Result<HttpResponse> { + let password_hash = hash(&form.password, DEFAULT_COST)?; + match sqlx::query!( + "insert into users(username, password) values ($1, $2)", + &form.username, + password_hash + ) + .execute(&state.db) + .await + { + Ok(_) => { + return Ok(HttpResponse::Ok().body( + render!( + templates::signup_html, + Some(Notification { + kind: NotificationKind::Info, + message: format!("you have successfully registered as {}", &form.username) + }) + ) + .unwrap(), + )) + } + Err(e) => { + match e { + sqlx::Error::Database(e) => { + if e.is_unique_violation() { + return Ok(HttpResponse::Conflict().body( + render!( + templates::signup_html, + Some(Notification { + kind: NotificationKind::Error, + message: format!( + "error: the username \"{}\" already exists", + &form.username + ) + }) + ) + .unwrap(), + )); + } + } + // TODO: log error + _ => {} + } + return Ok(HttpResponse::InternalServerError().body( + render!( + templates::signup_html, + Some(Notification { + kind: NotificationKind::Error, + message: "there was an internal server error. please try again later." + .to_owned() + }) + ) + .unwrap(), + )); + } + }; +} diff --git a/src/routes/static.rs b/src/routes/static.rs new file mode 100644 index 0000000..cabc6f8 --- /dev/null +++ b/src/routes/static.rs @@ -0,0 +1,22 @@ +use std::time::{Duration, SystemTime}; + +use actix_web::{http::header, web, HttpResponse}; + +use crate::templates::statics::StaticFile; + +static FAR: Duration = Duration::from_secs(180 * 24 * 60 * 60); + +pub async fn file(path: web::Path<String>) -> HttpResponse { + let name = &path.into_inner(); + if let Some(data) = StaticFile::get(name) { + let far_expires = SystemTime::now() + FAR; + HttpResponse::Ok() + .insert_header(header::Expires(far_expires.into())) + .insert_header(header::ContentType(data.mime.clone())) + .body(data.content) + } else { + HttpResponse::NotFound() + .reason("No such static file.") + .finish() + } +} diff --git a/src/routes/users.rs b/src/routes/users.rs new file mode 100644 index 0000000..2ad9ede --- /dev/null +++ b/src/routes/users.rs @@ -0,0 +1,16 @@ +use actix_web::{get, web, HttpResponse}; + +use crate::templates; +use crate::users::User; +use crate::{Pinussy, Result}; + +#[get("/users")] +async fn get(state: web::Data<Pinussy>) -> Result<HttpResponse> { + let users: Vec<User> = sqlx::query_as("select * from users") + .fetch_all(&state.db) + .await + // TODO: no unwrap + .unwrap(); + println!("lol"); + Ok(HttpResponse::Ok().body(render!(templates::users_html, users).unwrap())) +} |