You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -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
10
src/util/env.rs
Normal 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())
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
53
src/util/routes.rs
Normal 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(""))
|
||||
}
|
||||
}
|
||||
@@ -51,5 +51,5 @@ pub fn validation_errors_to_string(errors: ValidationErrors, adder: Option<Strin
|
||||
}
|
||||
}
|
||||
|
||||
"".to_string()
|
||||
String::new()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user