refactor
All checks were successful
ci/woodpecker/push/test Pipeline was successful

This commit is contained in:
JMARyA 2024-12-29 20:39:10 +01:00
parent 3837302161
commit 439467f730
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
10 changed files with 45 additions and 32 deletions

View file

@ -5,7 +5,7 @@ fn main() {
let url = "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"; let url = "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js";
let dest_path = Path::new("src/htmx.min.js"); let dest_path = Path::new("src/htmx.min.js");
println!("Downloading htmx.min.js from {}", url); println!("Downloading htmx.min.js from {url}");
let response = reqwest::blocking::get(url) let response = reqwest::blocking::get(url)
.expect("Failed to send HTTP request") .expect("Failed to send HTTP request")
.error_for_status() .error_for_status()
@ -13,7 +13,7 @@ fn main() {
let content = response.bytes().expect("Failed to read response body"); let content = response.bytes().expect("Failed to read response body");
fs::write(&dest_path, &content).expect("Failed to write htmx.min.js to destination"); fs::write(dest_path, &content).expect("Failed to write htmx.min.js to destination");
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
} }

View file

@ -24,6 +24,7 @@ pub struct Session {
pub kind: SessionKind, pub kind: SessionKind,
} }
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] #[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "session_kind", rename_all = "lowercase")] #[sqlx(type_name = "session_kind", rename_all = "lowercase")]
pub enum SessionKind { pub enum SessionKind {
@ -59,7 +60,7 @@ impl Sessions for User {
} }
/// End a user session /// End a user session
async fn end_session(&self) -> () { async fn end_session(&self) {
sqlx::query("DELETE FROM user_session WHERE token = $1") sqlx::query("DELETE FROM user_session WHERE token = $1")
.bind(&self.session) .bind(&self.session)
.execute(get_pg!()) .execute(get_pg!())

View file

@ -81,6 +81,7 @@ impl User {
/// Change the password of a User /// Change the password of a User
/// ///
/// Returns a Result indicating whether the password change was successful or not /// Returns a Result indicating whether the password change was successful or not
#[must_use]
pub async fn passwd(self, old: &str, new: &str) -> Result<(), ()> { pub async fn passwd(self, old: &str, new: &str) -> Result<(), ()> {
if self.verify_pw(old) { if self.verify_pw(old) {
sqlx::query("UPDATE users SET \"password\" = $1 WHERE username = $2;") sqlx::query("UPDATE users SET \"password\" = $1 WHERE username = $2;")
@ -97,6 +98,7 @@ impl User {
} }
/// Find all users in the system /// Find all users in the system
#[must_use]
pub async fn find_all() -> Vec<Self> { pub async fn find_all() -> Vec<Self> {
sqlx::query_as("SELECT * FROM users") sqlx::query_as("SELECT * FROM users")
.fetch_all(get_pg!()) .fetch_all(get_pg!())
@ -105,6 +107,7 @@ impl User {
} }
/// Check if the user is an admin /// Check if the user is an admin
#[must_use]
pub const fn is_admin(&self) -> bool { pub const fn is_admin(&self) -> bool {
matches!(self.user_role, UserRole::Admin) matches!(self.user_role, UserRole::Admin)
} }
@ -112,6 +115,7 @@ impl User {
/// Verify that a provided password matches the hashed password for the user /// Verify that a provided password matches the hashed password for the user
/// ///
/// Returns a boolean indicating whether the passwords match or not /// Returns a boolean indicating whether the passwords match or not
#[must_use]
pub fn verify_pw(&self, password: &str) -> bool { pub fn verify_pw(&self, password: &str) -> bool {
bcrypt::verify(password, &self.password).unwrap() bcrypt::verify(password, &self.password).unwrap()
} }
@ -127,13 +131,12 @@ impl ToAPI for User {
} }
/// extracts a user from a request with `session` cookie /// extracts a user from a request with `session` cookie
async fn extract_user<'r>(request: &'r Request<'_>) -> Option<User> { async fn extract_user(request: &Request<'_>) -> Option<User> {
if let Some(session_id) = request.cookies().get("session") { if let Some(session_id) = request.cookies().get("session") {
if let Some(user) = User::from_session(session_id.value()).await { if let Some(user) = User::from_session(session_id.value()).await {
return Some(user); return Some(user);
} else {
return None;
} }
return None;
} }
None None
@ -146,9 +149,8 @@ impl<'r> FromRequest<'r> for User {
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
if let Some(user) = extract_user(request).await { if let Some(user) = extract_user(request).await {
return Outcome::Success(user); return Outcome::Success(user);
} else {
return Outcome::Error((Status::Unauthorized, ()));
} }
Outcome::Error((Status::Unauthorized, ()))
} }
} }
@ -164,9 +166,8 @@ impl<'r> FromRequest<'r> for APIUser {
Some(key) => { Some(key) => {
if let Some(user) = User::from_session(key).await { if let Some(user) = User::from_session(key).await {
return Outcome::Success(APIUser(user)); return Outcome::Success(APIUser(user));
} else {
return Outcome::Error((Status::Unauthorized, ()));
} }
return Outcome::Error((Status::Unauthorized, ()));
} }
None => Outcome::Error((Status::Unauthorized, ())), None => Outcome::Error((Status::Unauthorized, ())),
} }
@ -202,9 +203,9 @@ impl<'r> FromRequest<'r> for MaybeUser {
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
if let Some(user) = extract_user(request).await { if let Some(user) = extract_user(request).await {
return Outcome::Success(MaybeUser::User(user)); return Outcome::Success(MaybeUser::User(user));
} else {
return Outcome::Success(MaybeUser::Anonymous);
} }
Outcome::Success(MaybeUser::Anonymous)
} }
} }
@ -215,13 +216,15 @@ impl From<MaybeUser> for Option<User> {
} }
impl MaybeUser { impl MaybeUser {
pub fn user(&self) -> Option<&User> { #[must_use]
pub const fn user(&self) -> Option<&User> {
match self { match self {
MaybeUser::User(user) => Some(user), MaybeUser::User(user) => Some(user),
MaybeUser::Anonymous => None, MaybeUser::Anonymous => None,
} }
} }
#[must_use]
pub fn take_user(self) -> Option<User> { pub fn take_user(self) -> Option<User> {
match self { match self {
MaybeUser::User(user) => Some(user), MaybeUser::User(user) => Some(user),
@ -255,8 +258,8 @@ impl<'r> FromRequest<'r> for AdminUser {
if user.is_admin() { if user.is_admin() {
return Outcome::Success(AdminUser(user)); return Outcome::Success(AdminUser(user));
} }
} else {
} }
Outcome::Error((Status::Unauthorized, ())) Outcome::Error((Status::Unauthorized, ()))
} }
} }

View file

@ -16,6 +16,7 @@
/// let formatted = format_date(&date); /// let formatted = format_date(&date);
/// assert_eq!(formatted, "2023-12-18"); /// assert_eq!(formatted, "2023-12-18");
/// ``` /// ```
#[must_use]
pub fn format_date(date: &chrono::NaiveDate) -> String { pub fn format_date(date: &chrono::NaiveDate) -> String {
// TODO : Implement custom formatting // TODO : Implement custom formatting
date.to_string() date.to_string()
@ -37,6 +38,7 @@ pub fn format_date(date: &chrono::NaiveDate) -> String {
/// let formatted = format_number(number); /// let formatted = format_number(number);
/// assert_eq!(formatted, "12345"); /// assert_eq!(formatted, "12345");
/// ``` /// ```
#[must_use]
pub fn format_number(num: i32) -> String { pub fn format_number(num: i32) -> String {
// TODO : Implement custom formatting // TODO : Implement custom formatting
num.to_string() num.to_string()
@ -66,14 +68,15 @@ pub fn format_number(num: i32) -> String {
/// let formatted = format_seconds_to_hhmmss(short_duration); /// let formatted = format_seconds_to_hhmmss(short_duration);
/// assert_eq!(formatted, "00:59"); /// assert_eq!(formatted, "00:59");
/// ``` /// ```
#[must_use]
pub fn format_seconds_to_hhmmss(seconds: f64) -> String { pub fn format_seconds_to_hhmmss(seconds: f64) -> String {
let total_seconds = seconds as u64; let total_seconds = seconds as u64;
let hours = total_seconds / 3600; let hours = total_seconds / 3600;
let minutes = (total_seconds % 3600) / 60; let minutes = (total_seconds % 3600) / 60;
let seconds = total_seconds % 60; let seconds = total_seconds % 60;
if hours != 0 { if hours != 0 {
format!("{:02}:{:02}:{:02}", hours, minutes, seconds) format!("{hours:02}:{minutes:02}:{seconds:02}")
} else { } else {
format!("{:02}:{:02}", minutes, seconds) format!("{minutes:02}:{seconds:02}")
} }
} }

View file

@ -15,7 +15,7 @@ pub mod result;
pub static PG: OnceCell<sqlx::PgPool> = OnceCell::const_new(); pub static PG: OnceCell<sqlx::PgPool> = OnceCell::const_new();
/// A macro to retrieve or initialize the PostgreSQL connection pool. /// A macro to retrieve or initialize the `PostgreSQL` connection pool.
/// ///
/// This macro provides a convenient way to access the `PgPool`. If the pool is not already initialized, /// This macro provides a convenient way to access the `PgPool`. If the pool is not already initialized,
/// it creates a new pool using the connection string from the `$DATABASE_URL` environment variable. /// it creates a new pool using the connection string from the `$DATABASE_URL` environment variable.

View file

@ -26,7 +26,8 @@ impl Shell {
/// ///
/// # Returns /// # Returns
/// A `Shell` instance encapsulating the provided HTML content and attributes. /// A `Shell` instance encapsulating the provided HTML content and attributes.
pub fn new( #[must_use]
pub const fn new(
head: PreEscaped<String>, head: PreEscaped<String>,
body_content: PreEscaped<String>, body_content: PreEscaped<String>,
body_class: Option<String>, body_class: Option<String>,
@ -46,6 +47,7 @@ impl Shell {
/// ///
/// # Returns /// # Returns
/// A `PreEscaped<String>` containing the full HTML page content. /// A `PreEscaped<String>` containing the full HTML page content.
#[must_use]
pub fn render(&self, content: PreEscaped<String>, title: &str) -> PreEscaped<String> { pub fn render(&self, content: PreEscaped<String>, title: &str) -> PreEscaped<String> {
html! { html! {
html { html {
@ -94,7 +96,9 @@ pub async fn render_page(
ctx: RequestContext, ctx: RequestContext,
shell: &Shell, shell: &Shell,
) -> StringResponse { ) -> StringResponse {
if !ctx.is_htmx { if ctx.is_htmx {
(Status::Ok, (ContentType::HTML, content.into_string()))
} else {
( (
Status::Ok, Status::Ok,
( (
@ -102,8 +106,6 @@ pub async fn render_page(
shell.render(content, title).into_string(), shell.render(content, title).into_string(),
), ),
) )
} else {
(Status::Ok, (ContentType::HTML, content.into_string()))
} }
} }
@ -119,6 +121,7 @@ pub async fn render_page(
/// ///
/// # Returns /// # Returns
/// A `PreEscaped<String>` containing the rendered HTML link element. /// A `PreEscaped<String>` containing the rendered HTML link element.
#[must_use]
pub fn htmx_link( pub fn htmx_link(
url: &str, url: &str,
class: &str, class: &str,
@ -142,6 +145,7 @@ pub fn htmx_link(
/// ///
/// # Returns /// # Returns
/// A `PreEscaped<String>` containing the rendered `<script>` element. /// A `PreEscaped<String>` containing the rendered `<script>` element.
#[must_use]
pub fn script(script: &str) -> PreEscaped<String> { pub fn script(script: &str) -> PreEscaped<String> {
html!( html!(
script { script {

View file

@ -62,6 +62,7 @@ pub fn to_uuid(id: &str) -> Result<uuid::Uuid, ApiError> {
/// ///
/// # Returns /// # Returns
/// * `ApiError` - A `BadRequest` error with a JSON payload describing the issue. /// * `ApiError` - A `BadRequest` error with a JSON payload describing the issue.
#[must_use]
pub fn no_uuid_error() -> ApiError { pub fn no_uuid_error() -> ApiError {
api_error("No valid UUID") api_error("No valid UUID")
} }
@ -76,6 +77,7 @@ pub fn no_uuid_error() -> ApiError {
/// ///
/// # Returns /// # Returns
/// * `ApiError` - A `BadRequest` error with a JSON payload describing the issue. /// * `ApiError` - A `BadRequest` error with a JSON payload describing the issue.
#[must_use]
pub fn api_error(msg: &str) -> ApiError { pub fn api_error(msg: &str) -> ApiError {
BadRequest(json!({ BadRequest(json!({
"error": msg "error": msg

View file

@ -14,6 +14,7 @@ pub struct DataResponse {
} }
impl DataResponse { impl DataResponse {
#[must_use]
pub fn new(data: Vec<u8>, content_type: &str, cache_duration: Option<u64>) -> Self { pub fn new(data: Vec<u8>, content_type: &str, cache_duration: Option<u64>) -> Self {
Self { Self {
data, data,
@ -47,11 +48,10 @@ impl<'r> Responder<'r, 'static> for DataResponse {
} }
// Add caching headers for static files // Add caching headers for static files
let cache_control_header = if let Some(duration) = self.cache_duration { let cache_control_header = self.cache_duration.map_or_else(
Header::new("Cache-Control", format!("public, max-age={}", duration)) || Header::new("Cache-Control", "no-cache"),
} else { |duration| Header::new("Cache-Control", format!("public, max-age={duration}")),
Header::new("Cache-Control", "no-cache") );
};
Ok(Response::build() Ok(Response::build()
.header(cache_control_header) .header(cache_control_header)

View file

@ -17,11 +17,7 @@ impl<'r> FromRequest<'r> for RequestContext {
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> { async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
rocket::outcome::Outcome::Success(RequestContext { rocket::outcome::Outcome::Success(RequestContext {
is_htmx: !req is_htmx: req.headers().get("HX-Request").next().is_some(),
.headers()
.get("HX-Request")
.collect::<Vec<&str>>()
.is_empty(),
}) })
} }
} }

View file

@ -48,6 +48,7 @@ impl RespondRaw for StringResponse {
/// ///
/// # Returns /// # Returns
/// A `StringResponse` with status `200 OK`, content type `application/json`, and the JSON-encoded body. /// A `StringResponse` with status `200 OK`, content type `application/json`, and the JSON-encoded body.
#[must_use]
pub fn respond_json(json: &serde_json::Value) -> StringResponse { pub fn respond_json(json: &serde_json::Value) -> StringResponse {
( (
Status::Ok, Status::Ok,
@ -65,6 +66,7 @@ pub fn respond_json(json: &serde_json::Value) -> StringResponse {
/// ///
/// # Returns /// # Returns
/// A `StringResponse` with status `200 OK`, content type `text/html`, and the HTML content as the body. /// A `StringResponse` with status `200 OK`, content type `text/html`, and the HTML content as the body.
#[must_use]
pub fn respond_html(html: &str) -> StringResponse { pub fn respond_html(html: &str) -> StringResponse {
(Status::Ok, (ContentType::HTML, html.to_string())) (Status::Ok, (ContentType::HTML, html.to_string()))
} }
@ -76,6 +78,7 @@ pub fn respond_html(html: &str) -> StringResponse {
/// ///
/// # Returns /// # Returns
/// A `StringResponse` with status `200 OK`, content type `text/javascript`, and the JS content as the body. /// A `StringResponse` with status `200 OK`, content type `text/javascript`, and the JS content as the body.
#[must_use]
pub fn respond_script(script: &str) -> StringResponse { pub fn respond_script(script: &str) -> StringResponse {
(Status::Ok, (ContentType::JavaScript, script.to_string())) (Status::Ok, (ContentType::JavaScript, script.to_string()))
} }
@ -89,6 +92,7 @@ pub fn respond_script(script: &str) -> StringResponse {
/// ///
/// # Returns /// # Returns
/// A `RawResponse` containing the provided status, content type, and body. /// A `RawResponse` containing the provided status, content type, and body.
pub fn respond_with(status: Status, content_type: ContentType, body: Vec<u8>) -> RawResponse { #[must_use]
pub const fn respond_with(status: Status, content_type: ContentType, body: Vec<u8>) -> RawResponse {
(status, (content_type, body)) (status, (content_type, body))
} }