diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 371 |
1 files changed, 17 insertions, 354 deletions
diff --git a/src/main.rs b/src/main.rs index b10d0cf..6a439ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1,13 @@ -#[macro_use] -mod actix_ructe; - -use std::time::{Duration, SystemTime}; - use actix_session::storage::CookieSessionStore; -use actix_session::{Session, SessionGetError, SessionInsertError, SessionMiddleware}; -use actix_web::body::{BoxBody, EitherBody, MessageBody}; -use actix_web::cookie::time::Error; +use actix_session::SessionMiddleware; use actix_web::cookie::Key; -use actix_web::dev::ServiceResponse; -use actix_web::http::header::LOCATION; -use actix_web::http::{header, StatusCode}; -use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; -use actix_web::web::Redirect; -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; - -static FAR: Duration = Duration::from_secs(180 * 24 * 60 * 60); - -type Result<T> = std::result::Result<T, PinussyError>; +use actix_web::http::StatusCode; +use actix_web::middleware::ErrorHandlers; +use actix_web::{web, App, HttpServer}; +use sqlx::postgres::PgPoolOptions; -#[derive(Clone)] -struct Pinussy { - db: Pool<Postgres>, -} +use pinussy::routes; +use pinussy::Pinussy; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -50,19 +30,19 @@ async fn main() -> std::io::Result<()> { )) .wrap( ErrorHandlers::new() - .handler(StatusCode::NOT_FOUND, render_404) - .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500), + .handler(StatusCode::NOT_FOUND, routes::error::render_404) + .handler(StatusCode::INTERNAL_SERVER_ERROR, routes::error::render_500), ) .app_data(web::Data::new(pinussy.clone())) - .service(web::resource("/static/{filename}").to(static_file)) - .service(home) + .service(web::resource("/static/{filename}").to(routes::r#static::file)) + .service(routes::home::get) // .service(home_auth) - .service(get_login) - .service(post_login) - .service(post_logout) - .service(get_signup) - .service(post_signup) - .service(get_users) + .service(routes::login::get) + .service(routes::login::post) + .service(routes::logout::post) + .service(routes::signup::get) + .service(routes::signup::post) + .service(routes::users::get) // .service(get_pins) // .service(post_pin) // .service(get_pin) @@ -73,320 +53,3 @@ async fn main() -> std::io::Result<()> { .run() .await } - -#[get("/")] -async fn home(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())); -} - -#[get("/signup")] -async fn get_signup() -> HttpResponse { - HttpResponse::Ok().body(render!(templates::signup_html, None).unwrap()) -} - -#[derive(Deserialize)] -struct SignupForm { - username: String, - password: String, -} - -#[post("/signup")] -async fn post_signup( - 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(), - )); - } - }; -} - -#[get("/login")] -async fn get_login() -> 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_login( - state: web::Data<Pinussy>, - session: Session, - form: web::Form<SignupForm>, -) -> 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: NotificationKind::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: NotificationKind::Error, - message: format!("the user \"{}\" does not exist", &form.username) - }) - ) - .unwrap(), - )); - } - Err(_) => { - return Ok(HttpResponse::InternalServerError().body( - render!( - templates::login_html, - Some(Notification { - kind: NotificationKind::Error, - message: "internal server error. please try again later".to_owned() - }) - ) - .unwrap(), - )); - } - } -} - -#[post("/logout")] -async fn post_logout(session: Session) -> HttpResponse { - session.purge(); - HttpResponse::SeeOther() - .insert_header((LOCATION, "/")) - .finish() -} - -#[derive(sqlx::Type)] -#[sqlx(type_name = "privacy", rename_all = "lowercase")] -enum Privacy { - Private, - Unlisted, - Public, -} - -#[derive(sqlx::FromRow)] -pub struct User { - id: i32, - username: String, - password: String, - email: Option<String>, - bio: Option<String>, - site: Option<String>, - privacy: Privacy, - 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") - .fetch_all(&state.db) - .await - // TODO: no unwrap - .unwrap(); - println!("lol"); - Ok(HttpResponse::Ok().body(render!(templates::users_html, users).unwrap())) -} - -async fn static_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() - } -} - -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.", - )) -} - -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(), - )) - })) -} - -#[derive(Debug)] -enum PinussyError { - Database(sqlx::Error), - SessionInsertError, - SessionGetError, - Bcrypt, -} - -impl From<SessionInsertError> for PinussyError { - fn from(_: SessionInsertError) -> Self { - Self::SessionInsertError - } -} - -impl From<SessionGetError> for PinussyError { - fn from(_: SessionGetError) -> Self { - Self::SessionGetError - } -} - -impl From<sqlx::Error> for PinussyError { - fn from(e: sqlx::Error) -> Self { - Self::Database(e) - } -} - -impl From<bcrypt::BcryptError> for PinussyError { - fn from(_: bcrypt::BcryptError) -> Self { - Self::Bcrypt - } -} - -impl std::fmt::Display for PinussyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for PinussyError {} - -impl ResponseError for PinussyError { - fn error_response(&self) -> HttpResponse<BoxBody> { - HttpResponse::new(self.status_code()) - } -} - -include!(concat!(env!("OUT_DIR"), "/templates.rs")); |