summaryrefslogtreecommitdiffstats
path: root/src/routes
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/routes/error.rs40
-rw-r--r--src/routes/home.rs21
-rw-r--r--src/routes/login.rs82
-rw-r--r--src/routes/logout.rs11
-rw-r--r--src/routes/mod.rs7
-rw-r--r--src/routes/signup.rs79
-rw-r--r--src/routes/static.rs22
-rw-r--r--src/routes/users.rs16
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()))
+}