diff options
| author | 2023-12-12 14:14:30 +0000 | |
|---|---|---|
| committer | 2023-12-12 14:14:30 +0000 | |
| commit | 370a25e5a0cbb95e2aa1cec55305b22aeaf99aa0 (patch) | |
| tree | 665163bc58c8e94320b843b1983376d9a67f99bf | |
| parent | 5dc4774ed3380762b4d7aadc86193af6073c456a (diff) | |
| download | pinussy-370a25e5a0cbb95e2aa1cec55305b22aeaf99aa0.tar.gz pinussy-370a25e5a0cbb95e2aa1cec55305b22aeaf99aa0.tar.bz2 pinussy-370a25e5a0cbb95e2aa1cec55305b22aeaf99aa0.zip | |
initial refactor
| -rw-r--r-- | src/error.rs | 48 | ||||
| -rw-r--r-- | src/lib.rs | 26 | ||||
| -rw-r--r-- | src/main.rs | 371 | ||||
| -rw-r--r-- | src/notification.rs | 20 | ||||
| -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 | ||||
| -rw-r--r-- | src/users.rs | 13 | ||||
| -rw-r--r-- | templates/base.rs.html | 2 | ||||
| -rw-r--r-- | templates/home.rs.html | 1 | ||||
| -rw-r--r-- | templates/login.rs.html | 2 | ||||
| -rw-r--r-- | templates/signup.rs.html | 2 | ||||
| -rw-r--r-- | templates/users.rs.html | 2 | 
18 files changed, 406 insertions, 359 deletions
| diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..e4999c8 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,48 @@ +use actix_session::{SessionGetError, SessionInsertError}; +use actix_web::{body::BoxBody, HttpResponse, ResponseError}; + +#[derive(Debug)] +pub 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()) +    } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..591d0c6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,26 @@ +#[macro_use] +mod actix_ructe; + +mod error; +mod notification; +pub mod routes; +mod users; + +use sqlx::{Pool, Postgres}; + +type Result<T> = std::result::Result<T, crate::error::PinussyError>; + +#[derive(Clone)] +pub struct Pinussy { +    pub db: Pool<Postgres>, +} + +#[derive(sqlx::Type)] +#[sqlx(type_name = "privacy", rename_all = "lowercase")] +pub enum Privacy { +    Private, +    Unlisted, +    Public, +} + +include!(concat!(env!("OUT_DIR"), "/templates.rs")); 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")); diff --git a/src/notification.rs b/src/notification.rs new file mode 100644 index 0000000..2283761 --- /dev/null +++ b/src/notification.rs @@ -0,0 +1,20 @@ +pub enum Kind { +    Info, +    Warning, +    Error, +} + +impl std::fmt::Display for Kind { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        match self { +            Kind::Info => f.write_str("info"), +            Kind::Warning => f.write_str("warning"), +            Kind::Error => f.write_str("error"), +        } +    } +} + +pub struct Notification { +    pub kind: Kind, +    pub message: String, +} 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())) +} diff --git a/src/users.rs b/src/users.rs new file mode 100644 index 0000000..bf41fd5 --- /dev/null +++ b/src/users.rs @@ -0,0 +1,13 @@ +use crate::Privacy; + +#[derive(sqlx::FromRow)] +pub struct User { +    pub id: i32, +    pub username: String, +    pub password: String, +    pub email: Option<String>, +    pub bio: Option<String>, +    pub site: Option<String>, +    pub privacy: Privacy, +    pub admin: bool, +} diff --git a/templates/base.rs.html b/templates/base.rs.html index 0bf70eb..434eb47 100644 --- a/templates/base.rs.html +++ b/templates/base.rs.html @@ -1,5 +1,5 @@  @use super::statics::*; -@use crate::Notification; +@use crate::notification::Notification;  @(user: Option<String>, notification: Option<Notification>, body: Content) diff --git a/templates/home.rs.html b/templates/home.rs.html index 882b2ed..6726646 100644 --- a/templates/home.rs.html +++ b/templates/home.rs.html @@ -1,5 +1,4 @@  @use super::base_html; -@use crate::User;  @(user: Option<String>) diff --git a/templates/login.rs.html b/templates/login.rs.html index 8547bd0..10efaf3 100644 --- a/templates/login.rs.html +++ b/templates/login.rs.html @@ -1,5 +1,5 @@  @use super::base_html; -@use crate::Notification; +@use crate::notification::Notification;  @(notification: Option<Notification>) diff --git a/templates/signup.rs.html b/templates/signup.rs.html index 4823698..179ec98 100644 --- a/templates/signup.rs.html +++ b/templates/signup.rs.html @@ -1,5 +1,5 @@  @use super::base_html; -@use crate::Notification; +@use crate::notification::Notification;  @(notification: Option<Notification>) diff --git a/templates/users.rs.html b/templates/users.rs.html index c5b98f9..b1a7b8d 100644 --- a/templates/users.rs.html +++ b/templates/users.rs.html @@ -1,5 +1,5 @@  @use super::base_html; -@use crate::User; +@use crate::users::User;  @(users: Vec<User>) | 
