Rustic cleanups, dedups and making the code less hard to read in general (#251)

* typos :help_me:

* (part 1/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 2/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 3/?) cut down some pretty major heap allocations here - more Bytes and BytesMuts, less Vec<u8>s

also I don't really understand why you need to `to_vec` when you don't really use it again afterwards

* (part 4/?) deduplicate error handling in backblaze logic

* (part 5/?) fixes, cleanups, refactors, and reformatting

* (part 6/?) cleanups and refactors

* remove loads of `as_str` in types that already are `Display`

* Revert "remove loads of `as_str` in types that already are `Display`"

This reverts commit 4f974310cfb167ceba03001d81388db4f0fbb509.

* reformat and move routes util to the util module

* use streams

* Run prepare + formatting issues

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Leo Chen
2021-10-12 11:26:59 +08:00
committed by GitHub
parent 0010119440
commit 13187de97d
53 changed files with 997 additions and 1129 deletions

View File

@@ -1,7 +1,12 @@
use crate::database;
use crate::database::models;
use crate::database::models::project_item::QueryProject;
use crate::models::users::{Role, User, UserId};
use crate::routes::ApiError;
use actix_web::http::HeaderMap;
use actix_web::web;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use thiserror::Error;
#[derive(Error, Debug)]
@@ -11,7 +16,7 @@ pub enum AuthenticationError {
#[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError),
#[error("Error while parsing JSON: {0}")]
SerDeError(#[from] serde_json::Error),
SerdeError(#[from] serde_json::Error),
#[error("Error while communicating to GitHub OAuth2: {0}")]
GithubError(#[from] reqwest::Error),
#[error("Invalid Authentication Credentials")]
@@ -65,7 +70,7 @@ where
avatar_url: result.avatar_url,
bio: result.bio,
created: result.created,
role: Role::from_string(&*result.role),
role: Role::from_string(&result.role),
}),
None => Err(AuthenticationError::InvalidCredentialsError),
}
@@ -116,3 +121,33 @@ where
_ => Err(AuthenticationError::InvalidCredentialsError),
}
}
pub async fn is_authorized(
project_data: &QueryProject,
user_option: &Option<User>,
pool: &web::Data<PgPool>,
) -> Result<bool, ApiError> {
let mut authorized = !project_data.status.is_hidden();
if let Some(user) = &user_option {
if !authorized {
if user.role.is_mod() {
authorized = true;
} else {
let user_id: database::models::ids::UserId = user.id.into();
let project_exists = sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM team_members WHERE team_id = $1 AND user_id = $2)",
project_data.inner.team_id as database::models::ids::TeamId,
user_id as database::models::ids::UserId,
)
.fetch_one(&***pool)
.await?
.exists;
authorized = project_exists.unwrap_or(false);
}
}
}
Ok(authorized)
}

10
src/util/env.rs Normal file
View File

@@ -0,0 +1,10 @@
use std::str::FromStr;
pub fn parse_var<T: FromStr>(var: &'static str) -> Option<T> {
dotenv::var(var).ok().and_then(|i| i.parse().ok())
}
pub fn parse_strings_from_var(var: &'static str) -> Option<Vec<String>> {
dotenv::var(var)
.ok()
.and_then(|s| serde_json::from_str::<Vec<String>>(&s).ok())
}

View File

@@ -1,20 +1,14 @@
pub fn get_image_content_type(extension: &str) -> Option<&'static str> {
let content_type = match &*extension {
"bmp" => "image/bmp",
"gif" => "image/gif",
"jpeg" | "jpg" | "jpe" => "image/jpeg",
"png" => "image/png",
"svg" | "svgz" => "image/svg+xml",
"webp" => "image/webp",
"rgb" => "image/x-rgb",
"mp4" => "video/mp4",
_ => "",
};
if !content_type.is_empty() {
Some(content_type)
} else {
None
match extension {
"bmp" => Some("image/bmp"),
"gif" => Some("image/gif"),
"jpeg" | "jpg" | "jpe" => Some("image/jpeg"),
"png" => Some("image/png"),
"svg" | "svgz" => Some("image/svg+xml"),
"webp" => Some("image/webp"),
"rgb" => Some("image/x-rgb"),
"mp4" => Some("video/mp4"),
_ => None,
}
}

View File

@@ -1,4 +1,6 @@
pub mod auth;
pub mod env;
pub mod ext;
pub mod routes;
pub mod validate;
pub mod webhook;

53
src/util/routes.rs Normal file
View File

@@ -0,0 +1,53 @@
use crate::routes::project_creation::CreateError;
use crate::routes::ApiError;
use actix_multipart::Field;
use actix_web::web::Payload;
use actix_web::HttpResponse;
use bytes::BytesMut;
use futures::StreamExt;
use serde::Serialize;
pub async fn read_from_payload(
payload: &mut Payload,
cap: usize,
err_msg: &'static str,
) -> Result<BytesMut, ApiError> {
let mut bytes = BytesMut::new();
while let Some(item) = payload.next().await {
if bytes.len() >= cap {
return Err(ApiError::InvalidInputError(String::from(err_msg)));
} else {
bytes.extend_from_slice(&item.map_err(|_| {
ApiError::InvalidInputError("Unable to parse bytes in payload sent!".to_string())
})?);
}
}
Ok(bytes)
}
pub async fn read_from_field(
field: &mut Field,
cap: usize,
err_msg: &'static str,
) -> Result<BytesMut, CreateError> {
let mut bytes = BytesMut::new();
while let Some(chunk) = field.next().await {
if bytes.len() >= cap {
return Err(CreateError::InvalidInput(String::from(err_msg)));
} else {
bytes.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
}
}
Ok(bytes)
}
pub(crate) fn ok_or_not_found<T, U>(version_data: Option<T>) -> Result<HttpResponse, ApiError>
where
U: From<T> + Serialize,
{
if let Some(data) = version_data {
Ok(HttpResponse::Ok().json(U::from(data)))
} else {
Ok(HttpResponse::NotFound().body(""))
}
}

View File

@@ -51,5 +51,5 @@ pub fn validation_errors_to_string(errors: ValidationErrors, adder: Option<Strin
}
}
"".to_string()
String::new()
}

View File

@@ -15,7 +15,7 @@ struct DiscordEmbed {
#[derive(Serialize)]
struct DiscordEmbedField {
pub name: String,
pub name: &'static str,
pub value: String,
pub inline: bool,
}
@@ -36,36 +36,36 @@ pub async fn send_discord_webhook(
) -> Result<(), reqwest::Error> {
let mut fields = vec![
DiscordEmbedField {
name: "id".to_string(),
name: "id",
value: project.id.to_string(),
inline: true,
},
DiscordEmbedField {
name: "project_type".to_string(),
value: project.project_type.to_string(),
name: "project_type",
value: project.project_type.clone(),
inline: true,
},
DiscordEmbedField {
name: "client_side".to_string(),
name: "client_side",
value: project.client_side.to_string(),
inline: true,
},
DiscordEmbedField {
name: "server_side".to_string(),
name: "server_side",
value: project.server_side.to_string(),
inline: true,
},
DiscordEmbedField {
name: "categories".to_string(),
name: "categories",
value: project.categories.join(", "),
inline: true,
},
];
if let Some(slug) = project.slug.clone() {
if let Some(ref slug) = project.slug {
fields.push(DiscordEmbedField {
name: "slug".to_string(),
value: slug,
name: "slug",
value: slug.clone(),
inline: true,
});
}
@@ -82,7 +82,7 @@ pub async fn send_discord_webhook(
title: project.title,
description: project.description,
timestamp: project.published,
color: 6137157,
color: 0x5DA545,
fields,
image: DiscordEmbedImage {
url: project.icon_url,