diff options
-rw-r--r-- | migrations/20231003193749_pinussy.sql | 6 | ||||
-rw-r--r-- | src/main.rs | 83 | ||||
-rw-r--r-- | templates/base.rs.html | 10 | ||||
-rw-r--r-- | templates/login.rs.html | 7 | ||||
-rw-r--r-- | templates/signup.rs.html | 7 | ||||
-rw-r--r-- | templates/users.rs.html | 2 |
6 files changed, 94 insertions, 21 deletions
diff --git a/migrations/20231003193749_pinussy.sql b/migrations/20231003193749_pinussy.sql index 24f3aa1..cba545e 100644 --- a/migrations/20231003193749_pinussy.sql +++ b/migrations/20231003193749_pinussy.sql @@ -4,7 +4,7 @@ create type file_type as enum ('image', 'video', 'audio', 'text', 'document', 's create table users ( id integer primary key generated always as identity, username varchar(32) not null, - unique(id, username), + unique(username), password varchar(128) not null, email varchar(128), bio text, @@ -23,7 +23,6 @@ create table sessions ( create table boards ( id integer primary key generated always as identity, idname varchar(256), - unique(id, idname), name varchar(256), description text, privacy privacy @@ -40,7 +39,6 @@ create table board_ownership ( create table pins ( id integer primary key generated always as identity, idname varchar(256), - unique(id, idname), subject varchar(256), notes text, privacy privacy @@ -65,7 +63,6 @@ create table pins_boards ( create table sources ( id integer primary key generated always as identity, idname varchar(256), - unique(id, idname), title varchar(256), author varchar(256), url varchar(256), @@ -85,7 +82,6 @@ create table pin_sources ( create table files ( id integer primary key generated always as identity, idname varchar(256), - unique(id, idname), thumbnail varchar(256), path varchar(256) not null, title varchar(256), diff --git a/src/main.rs b/src/main.rs index eb7f0e8..c1520ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, ResponseError}; use bcrypt::{hash, verify, DEFAULT_COST}; use serde::Deserialize; +use sqlx::postgres::PgDatabaseError; use sqlx::{postgres::PgPoolOptions, Pool, Postgres}; use templates::statics::StaticFile; @@ -67,7 +68,7 @@ async fn home() -> HttpResponse { #[get("/signup")] async fn get_signup() -> HttpResponse { - HttpResponse::Ok().body(render!(templates::signup_html).unwrap()) + HttpResponse::Ok().body(render!(templates::signup_html, None).unwrap()) } #[derive(Deserialize)] @@ -82,31 +83,78 @@ async fn post_signup( form: web::Form<SignupForm>, ) -> Result<HttpResponse> { let password_hash = hash(&form.password, DEFAULT_COST)?; - sqlx::query!( + match sqlx::query!( "insert into users(username, password) values ($1, $2)", &form.username, password_hash ) .execute(&state.db) - .await?; - Ok(HttpResponse::Ok().body(render!(templates::signup_html).unwrap())) + .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(), + )); + } + }; } #[get("/login")] async fn get_login() -> HttpResponse { - HttpResponse::Ok().body(render!(templates::login_html).unwrap()) + HttpResponse::Ok().body(render!(templates::login_html, None).unwrap()) } #[derive(Deserialize)] struct LoginForm { username: String, password: String, - rememberme: bool, + rememberme: Option<String>, } #[post("/login")] async fn post_login(form: web::Form<LoginForm>) -> Result<HttpResponse> { - Ok(HttpResponse::Ok().body(render!(templates::login_html).unwrap())) + Ok(HttpResponse::Ok().body(render!(templates::login_html, None).unwrap())) } #[derive(sqlx::Type)] @@ -129,6 +177,27 @@ pub struct User { admin: bool, } +pub enum NotificationKind { + Info, + Warning, + Error, +} + +impl std::fmt::Display for NotificationKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NotificationKind::Info => f.write_str("info"), + NotificationKind::Warning => f.write_str("warning"), + NotificationKind::Error => f.write_str("error"), + } + } +} + +pub struct Notification { + kind: NotificationKind, + message: String, +} + #[get("/users")] async fn get_users(state: web::Data<Pinussy>) -> Result<HttpResponse> { let users: Vec<User> = sqlx::query_as("select * from users") diff --git a/templates/base.rs.html b/templates/base.rs.html index 5b1a324..64a8d4e 100644 --- a/templates/base.rs.html +++ b/templates/base.rs.html @@ -1,6 +1,7 @@ @use super::statics::*; +@use crate::Notification; -@(authenticated: bool, body: Content) +@(authenticated: bool, notification: Option<Notification>, body: Content) <!DOCTYPE html> <html> @@ -15,11 +16,16 @@ <body> <nav> @if authenticated { - logout + <form action="/logout" method="post"> + <button type="submit">logout</button> + </form> } else { <a href="/login">log in</a> } </nav> + @if let Some(notification) = notification { + <div class="notification @notification.kind">@notification.message</div> + } @:body() </body> diff --git a/templates/login.rs.html b/templates/login.rs.html index 349263c..c87866c 100644 --- a/templates/login.rs.html +++ b/templates/login.rs.html @@ -1,8 +1,9 @@ @use super::base_html; +@use crate::Notification; -@() +@(notification: Option<Notification>) -@:base_html(false, { +@:base_html(false, None, { <form action="/login" method="post"> <label for="username">username:</label> <input type="text" id="username" name="username" required="true" /> @@ -12,4 +13,4 @@ <input type="checkbox" id="rememberme" name="rememberme" /> <button type="submit">log in</button> </form> -})
\ No newline at end of file +}) diff --git a/templates/signup.rs.html b/templates/signup.rs.html index 354dae6..a482fc2 100644 --- a/templates/signup.rs.html +++ b/templates/signup.rs.html @@ -1,8 +1,9 @@ @use super::base_html; +@use crate::Notification; -@() +@(notification: Option<Notification>) -@:base_html(false, { +@:base_html(false, notification, { <form action="/signup" method="post"> <label for="username">username:</label> <input type="text" id="username" name="username" required="true" /> @@ -10,4 +11,4 @@ <input type="text" id="password" name="password" required="true" /> <button type="submit">sign up</button> </form> -})
\ No newline at end of file +}) diff --git a/templates/users.rs.html b/templates/users.rs.html index 9b9be30..ab1ab0f 100644 --- a/templates/users.rs.html +++ b/templates/users.rs.html @@ -3,7 +3,7 @@ @(users: Vec<User>) -@:base_html(false, { +@:base_html(false, None, { <ul> @for user in users { <li>@user.username</li> |