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,9 +1,9 @@
|
||||
use crate::health::status::test_database;
|
||||
use crate::health::SEARCH_READY;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{get, HttpResponse};
|
||||
use serde_json::json;
|
||||
use crate::health::status::test_database;
|
||||
use actix_web::web::Data;
|
||||
use sqlx::PgPool;
|
||||
use crate::health::SEARCH_READY;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
#[get("/health")]
|
||||
@@ -15,17 +15,17 @@ pub async fn health_get(client: Data<PgPool>) -> HttpResponse {
|
||||
"ready": false,
|
||||
"reason": "Database connection error"
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(data)
|
||||
return HttpResponse::InternalServerError().json(data);
|
||||
}
|
||||
if !SEARCH_READY.load(Ordering::Acquire) {
|
||||
let data = json!({
|
||||
"ready": false,
|
||||
"reason": "Indexing is not finished"
|
||||
});
|
||||
return HttpResponse::InternalServerError().json(data)
|
||||
return HttpResponse::InternalServerError().json(data);
|
||||
}
|
||||
HttpResponse::Ok().json(json!({
|
||||
"ready": true,
|
||||
"reason": "Everything is OK"
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use actix_web::web;
|
||||
|
||||
mod v1;
|
||||
pub use v1::v1_config;
|
||||
|
||||
mod auth;
|
||||
mod health;
|
||||
mod index;
|
||||
mod maven;
|
||||
mod moderation;
|
||||
mod not_found;
|
||||
mod notifications;
|
||||
mod project_creation;
|
||||
pub(crate) mod project_creation;
|
||||
mod projects;
|
||||
mod reports;
|
||||
mod tags;
|
||||
@@ -18,15 +17,15 @@ mod users;
|
||||
mod version_creation;
|
||||
mod version_file;
|
||||
mod versions;
|
||||
mod health;
|
||||
|
||||
pub use auth::config as auth_config;
|
||||
pub use tags::config as tags_config;
|
||||
|
||||
pub use self::index::index_get;
|
||||
pub use self::health::health_get;
|
||||
pub use self::index::index_get;
|
||||
pub use self::not_found::not_found;
|
||||
use crate::file_hosting::FileHostingError;
|
||||
use actix_web::web;
|
||||
|
||||
pub fn v2_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::ApiError;
|
||||
use crate::database;
|
||||
use crate::models::projects::{Project, ProjectStatus};
|
||||
use crate::models::projects::ProjectStatus;
|
||||
use crate::util::auth::check_is_moderator_from_headers;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use serde::Deserialize;
|
||||
@@ -43,10 +43,10 @@ pub async fn get_projects(
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects: Vec<Project> = database::Project::get_many_full(project_ids, &**pool)
|
||||
let projects: Vec<_> = database::Project::get_many_full(project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(super::projects::convert_project)
|
||||
.map(crate::models::projects::Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::database;
|
||||
use crate::models::ids::NotificationId;
|
||||
use crate::models::notifications::{Notification, NotificationAction};
|
||||
use crate::models::notifications::Notification;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
@@ -20,22 +20,25 @@ pub async fn notifications_get(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
let notification_ids = serde_json::from_str::<Vec<NotificationId>>(&*ids.ids)?
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect();
|
||||
// TODO: this is really confusingly named.
|
||||
use database::models::notification_item::Notification as DBNotification;
|
||||
use database::models::NotificationId as DBNotificationId;
|
||||
|
||||
let notifications_data =
|
||||
let notification_ids: Vec<DBNotificationId> =
|
||||
serde_json::from_str::<Vec<NotificationId>>(ids.ids.as_str())?
|
||||
.into_iter()
|
||||
.map(DBNotificationId::from)
|
||||
.collect();
|
||||
|
||||
let notifications_data: Vec<DBNotification> =
|
||||
database::models::notification_item::Notification::get_many(notification_ids, &**pool)
|
||||
.await?;
|
||||
|
||||
let mut notifications: Vec<Notification> = Vec::new();
|
||||
|
||||
for notification in notifications_data {
|
||||
if notification.user_id == user.id.into() || user.role.is_mod() {
|
||||
notifications.push(convert_notification(notification));
|
||||
}
|
||||
}
|
||||
let notifications: Vec<Notification> = notifications_data
|
||||
.into_iter()
|
||||
.filter(|n| n.user_id == user.id.into() || user.role.is_mod())
|
||||
.map(Notification::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(notifications))
|
||||
}
|
||||
@@ -55,7 +58,7 @@ pub async fn notification_get(
|
||||
|
||||
if let Some(data) = notification_data {
|
||||
if user.id == data.user_id.into() || user.role.is_mod() {
|
||||
Ok(HttpResponse::Ok().json(convert_notification(data)))
|
||||
Ok(HttpResponse::Ok().json(Notification::from(data)))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
@@ -64,29 +67,6 @@ pub async fn notification_get(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_notification(
|
||||
notif: database::models::notification_item::Notification,
|
||||
) -> Notification {
|
||||
Notification {
|
||||
id: notif.id.into(),
|
||||
user_id: notif.user_id.into(),
|
||||
type_: notif.notification_type,
|
||||
title: notif.title,
|
||||
text: notif.text,
|
||||
link: notif.link,
|
||||
read: notif.read,
|
||||
created: notif.created,
|
||||
actions: notif
|
||||
.actions
|
||||
.into_iter()
|
||||
.map(|x| NotificationAction {
|
||||
title: x.title,
|
||||
action_route: (x.action_route_method, x.action_route),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("{id}")]
|
||||
pub async fn notification_delete(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::models::users::UserId;
|
||||
use crate::routes::version_creation::InitialVersionData;
|
||||
use crate::search::indexing::IndexingError;
|
||||
use crate::util::auth::{get_user_from_headers, AuthenticationError};
|
||||
use crate::util::routes::read_from_field;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_multipart::{Field, Multipart};
|
||||
use actix_web::http::StatusCode;
|
||||
@@ -255,7 +256,6 @@ pub async fn project_create(
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Project Creation Steps:
|
||||
@@ -449,18 +449,12 @@ pub async fn project_create_inner(
|
||||
}
|
||||
|
||||
if let Some(item) = gallery_items.iter().find(|x| x.item == name) {
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
const FILE_SIZE_CAP: usize = 5 * (1 << 20);
|
||||
|
||||
if data.len() >= FILE_SIZE_CAP {
|
||||
return Err(CreateError::InvalidInput(String::from(
|
||||
"Gallery image exceeds the maximum of 5MiB.",
|
||||
)));
|
||||
} else {
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
}
|
||||
}
|
||||
let data = read_from_field(
|
||||
&mut field,
|
||||
5 * (1 << 20),
|
||||
"Gallery image exceeds the maximum of 5MiB.",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let hash = sha1::Sha1::from(&data).hexdigest();
|
||||
let (_, file_extension) =
|
||||
@@ -470,7 +464,7 @@ pub async fn project_create_inner(
|
||||
|
||||
let url = format!("data/{}/images/{}.{}", project_id, hash, file_extension);
|
||||
let upload_data = file_host
|
||||
.upload_file(content_type, &url, data.to_vec())
|
||||
.upload_file(content_type, &url, data.freeze())
|
||||
.await?;
|
||||
|
||||
uploaded_files.push(UploadedFile {
|
||||
@@ -804,22 +798,13 @@ async fn process_icon_upload(
|
||||
cdn_url: &str,
|
||||
) -> Result<String, CreateError> {
|
||||
if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) {
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
if data.len() >= 262144 {
|
||||
return Err(CreateError::InvalidInput(String::from(
|
||||
"Icons must be smaller than 256KiB",
|
||||
)));
|
||||
} else {
|
||||
data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?);
|
||||
}
|
||||
}
|
||||
let data = read_from_field(&mut field, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(
|
||||
content_type,
|
||||
&format!("data/{}/icon.{}", project_id, file_extension),
|
||||
data,
|
||||
data.freeze(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ use crate::database;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::projects::{
|
||||
DonationLink, GalleryItem, License, ModeratorMessage, ProjectId, ProjectStatus, SearchRequest,
|
||||
SideType,
|
||||
DonationLink, Project, ProjectId, ProjectStatus, SearchRequest, SideType,
|
||||
};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::routes::ApiError;
|
||||
use crate::search::indexing::queue::CreationQueue;
|
||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::auth::{get_user_from_headers, is_authorized};
|
||||
use crate::util::routes::read_from_payload;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
@@ -48,36 +48,16 @@ pub async fn projects_get(
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let mut projects = Vec::new();
|
||||
|
||||
for project_data in projects_data {
|
||||
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);
|
||||
}
|
||||
let projects: Vec<_> = futures::stream::iter(projects_data)
|
||||
.filter_map(|data| async {
|
||||
if is_authorized(&data, &user_option, &pool).await.ok()? {
|
||||
Some(Project::from(data))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
if authorized {
|
||||
projects.push(convert_project(project_data));
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
@@ -97,37 +77,11 @@ pub async fn project_get(
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
if let Some(data) = project_data {
|
||||
let mut authorized = !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)",
|
||||
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);
|
||||
}
|
||||
}
|
||||
if is_authorized(&data, &user_option, &pool).await? {
|
||||
return Ok(HttpResponse::Ok().json(Project::from(data)));
|
||||
}
|
||||
|
||||
if authorized {
|
||||
return Ok(HttpResponse::Ok().json(convert_project(data)));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -189,12 +143,12 @@ pub async fn dependency_list(
|
||||
|
||||
let projects = projects_result?
|
||||
.into_iter()
|
||||
.map(convert_project)
|
||||
.collect::<Vec<models::projects::Project>>();
|
||||
.map(models::projects::Project::from)
|
||||
.collect::<Vec<_>>();
|
||||
let versions = versions_result?
|
||||
.into_iter()
|
||||
.map(super::versions::convert_version)
|
||||
.collect::<Vec<models::projects::Version>>();
|
||||
.map(models::projects::Version::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(DependencyInfo { projects, versions }))
|
||||
} else {
|
||||
@@ -202,71 +156,6 @@ pub async fn dependency_list(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_project(
|
||||
data: database::models::project_item::QueryProject,
|
||||
) -> models::projects::Project {
|
||||
let m = data.inner;
|
||||
|
||||
models::projects::Project {
|
||||
id: m.id.into(),
|
||||
slug: m.slug,
|
||||
project_type: data.project_type,
|
||||
team: m.team_id.into(),
|
||||
title: m.title,
|
||||
description: m.description,
|
||||
body: m.body,
|
||||
body_url: m.body_url,
|
||||
published: m.published,
|
||||
updated: m.updated,
|
||||
status: data.status,
|
||||
moderator_message: if let Some(message) = m.moderation_message {
|
||||
Some(ModeratorMessage {
|
||||
message,
|
||||
body: m.moderation_message_body,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
license: License {
|
||||
id: data.license_id,
|
||||
name: data.license_name,
|
||||
url: m.license_url,
|
||||
},
|
||||
client_side: data.client_side,
|
||||
server_side: data.server_side,
|
||||
downloads: m.downloads as u32,
|
||||
followers: m.follows as u32,
|
||||
categories: data.categories,
|
||||
versions: data.versions.into_iter().map(|v| v.into()).collect(),
|
||||
icon_url: m.icon_url,
|
||||
issues_url: m.issues_url,
|
||||
source_url: m.source_url,
|
||||
wiki_url: m.wiki_url,
|
||||
discord_url: m.discord_url,
|
||||
donation_urls: Some(
|
||||
data.donation_urls
|
||||
.into_iter()
|
||||
.map(|d| DonationLink {
|
||||
id: d.platform_short,
|
||||
platform: d.platform_name,
|
||||
url: d.url,
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
gallery: data
|
||||
.gallery_items
|
||||
.into_iter()
|
||||
.map(|x| GalleryItem {
|
||||
url: x.image_url,
|
||||
featured: x.featured,
|
||||
title: x.title,
|
||||
description: x.description,
|
||||
created: x.created,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A project returned from the API
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
pub struct EditProject {
|
||||
@@ -476,7 +365,7 @@ pub async fn project_edit(
|
||||
|
||||
if let Ok(webhook_url) = dotenv::var("MODERATION_DISCORD_WEBHOOK") {
|
||||
crate::util::webhook::send_discord_webhook(
|
||||
convert_project(project_item.clone()),
|
||||
Project::from(project_item.clone()),
|
||||
webhook_url,
|
||||
)
|
||||
.await
|
||||
@@ -959,30 +848,15 @@ pub async fn project_icon_edit(
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes = web::BytesMut::new();
|
||||
while let Some(item) = payload.next().await {
|
||||
if bytes.len() >= 262144 {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Icons must be smaller than 256KiB",
|
||||
)));
|
||||
} else {
|
||||
bytes.extend_from_slice(&item.map_err(|_| {
|
||||
ApiError::InvalidInputError(
|
||||
"Unable to parse bytes in payload sent!".to_string(),
|
||||
)
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB").await?;
|
||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||
|
||||
let project_id: ProjectId = project_item.id.into();
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(
|
||||
content_type,
|
||||
&format!("data/{}/{}.{}", project_id, hash, ext.ext),
|
||||
bytes.to_vec(),
|
||||
bytes.freeze(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1126,29 +1000,18 @@ pub async fn add_gallery_item(
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes = web::BytesMut::new();
|
||||
while let Some(item) = payload.next().await {
|
||||
const FILE_SIZE_CAP: usize = 5 * (1 << 20);
|
||||
|
||||
if bytes.len() >= FILE_SIZE_CAP {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Gallery image exceeds the maximum of 5MiB.",
|
||||
)));
|
||||
} else {
|
||||
bytes.extend_from_slice(&item.map_err(|_| {
|
||||
ApiError::InvalidInputError(
|
||||
"Unable to parse bytes in payload sent!".to_string(),
|
||||
)
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = read_from_payload(
|
||||
&mut payload,
|
||||
5 * (1 << 20),
|
||||
"Gallery image exceeds the maximum of 5MiB.",
|
||||
)
|
||||
.await?;
|
||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||
|
||||
let id: ProjectId = project_item.id.into();
|
||||
let url = format!("data/{}/images/{}.{}", id, hash, &*ext.ext);
|
||||
file_host
|
||||
.upload_file(content_type, &url, bytes.to_vec())
|
||||
.upload_file(content_type, &url, bytes.freeze())
|
||||
.await?;
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::database::models::notification_item::{NotificationActionBuilder, NotificationBuilder};
|
||||
use crate::database::models::team_item::QueryTeamMember;
|
||||
use crate::database::models::TeamMember;
|
||||
use crate::models::ids::ProjectId;
|
||||
use crate::models::teams::{Permissions, TeamId};
|
||||
@@ -32,19 +31,19 @@ pub async fn team_members_get_project(
|
||||
.map_err(ApiError::DatabaseError)?;
|
||||
|
||||
if team_member.is_some() {
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = members_data
|
||||
let team_members: Vec<_> = members_data
|
||||
.into_iter()
|
||||
.map(|data| convert_team_member(data, false))
|
||||
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
||||
.collect();
|
||||
|
||||
return Ok(HttpResponse::Ok().json(team_members));
|
||||
}
|
||||
}
|
||||
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = members_data
|
||||
let team_members: Vec<_> = members_data
|
||||
.into_iter()
|
||||
.filter(|x| x.accepted)
|
||||
.map(|data| convert_team_member(data, true))
|
||||
.map(|data| crate::models::teams::TeamMember::from(data, true))
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(team_members))
|
||||
@@ -53,23 +52,6 @@ pub async fn team_members_get_project(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_team_member(
|
||||
data: QueryTeamMember,
|
||||
override_permissions: bool,
|
||||
) -> crate::models::teams::TeamMember {
|
||||
crate::models::teams::TeamMember {
|
||||
team_id: data.team_id.into(),
|
||||
user: super::users::convert_user(data.user),
|
||||
role: data.role,
|
||||
permissions: if override_permissions {
|
||||
None
|
||||
} else {
|
||||
Some(data.permissions)
|
||||
},
|
||||
accepted: data.accepted,
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{id}/members")]
|
||||
pub async fn team_members_get(
|
||||
req: HttpRequest,
|
||||
@@ -87,19 +69,19 @@ pub async fn team_members_get(
|
||||
.map_err(ApiError::DatabaseError)?;
|
||||
|
||||
if team_member.is_some() {
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = members_data
|
||||
let team_members: Vec<_> = members_data
|
||||
.into_iter()
|
||||
.map(|data| convert_team_member(data, false))
|
||||
.map(|data| crate::models::teams::TeamMember::from(data, false))
|
||||
.collect();
|
||||
|
||||
return Ok(HttpResponse::Ok().json(team_members));
|
||||
}
|
||||
}
|
||||
|
||||
let team_members: Vec<crate::models::teams::TeamMember> = members_data
|
||||
let team_members: Vec<_> = members_data
|
||||
.into_iter()
|
||||
.filter(|x| x.accepted)
|
||||
.map(|data| convert_team_member(data, true))
|
||||
.map(|data| crate::models::teams::TeamMember::from(data, true))
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(team_members))
|
||||
|
||||
@@ -3,12 +3,11 @@ use crate::file_hosting::FileHost;
|
||||
use crate::models::notifications::Notification;
|
||||
use crate::models::projects::{Project, ProjectStatus};
|
||||
use crate::models::users::{Role, UserId};
|
||||
use crate::routes::notifications::convert_notification;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::routes::read_from_payload;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse};
|
||||
use futures::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -42,7 +41,7 @@ pub async fn users_get(
|
||||
|
||||
let users_data = User::get_many(user_ids, &**pool).await?;
|
||||
|
||||
let users: Vec<crate::models::users::User> = users_data.into_iter().map(convert_user).collect();
|
||||
let users: Vec<crate::models::users::User> = users_data.into_iter().map(From::from).collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(users))
|
||||
}
|
||||
@@ -68,27 +67,13 @@ pub async fn user_get(
|
||||
}
|
||||
|
||||
if let Some(data) = user_data {
|
||||
let response = convert_user(data);
|
||||
let response: crate::models::users::User = data.into();
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_user(data: crate::database::models::user_item::User) -> crate::models::users::User {
|
||||
crate::models::users::User {
|
||||
id: data.id.into(),
|
||||
github_id: data.github_id.map(|i| i as u64),
|
||||
username: data.username,
|
||||
name: data.name,
|
||||
email: None,
|
||||
avatar_url: data.avatar_url,
|
||||
bio: data.bio,
|
||||
created: data.created,
|
||||
role: Role::from_string(&*data.role),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("{user_id}/projects")]
|
||||
pub async fn projects_list(
|
||||
req: HttpRequest,
|
||||
@@ -114,11 +99,11 @@ pub async fn projects_list(
|
||||
User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool).await?
|
||||
};
|
||||
|
||||
let response = crate::database::Project::get_many_full(project_data, &**pool)
|
||||
let response: Vec<_> = crate::database::Project::get_many_full(project_data, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(super::projects::convert_project)
|
||||
.collect::<Vec<Project>>();
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
} else {
|
||||
@@ -337,26 +322,15 @@ pub async fn user_icon_edit(
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes = web::BytesMut::new();
|
||||
while let Some(item) = payload.next().await {
|
||||
if bytes.len() >= 262144 {
|
||||
return Err(ApiError::InvalidInputError(String::from(
|
||||
"Icons must be smaller than 256KiB",
|
||||
)));
|
||||
} else {
|
||||
bytes.extend_from_slice(&item.map_err(|_| {
|
||||
ApiError::InvalidInputError(
|
||||
"Unable to parse bytes in payload sent!".to_string(),
|
||||
)
|
||||
})?);
|
||||
}
|
||||
}
|
||||
let bytes =
|
||||
read_from_payload(&mut payload, 262144, "Icons must be smaller than 256KiB")
|
||||
.await?;
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(
|
||||
content_type,
|
||||
&format!("user/{}/icon.{}", user_id, ext.ext),
|
||||
bytes.to_vec(),
|
||||
bytes.freeze(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -468,11 +442,11 @@ pub async fn user_follows(
|
||||
.try_collect::<Vec<crate::database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects = crate::database::Project::get_many_full(project_ids, &**pool)
|
||||
let projects: Vec<_> = crate::database::Project::get_many_full(project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(super::projects::convert_project)
|
||||
.collect::<Vec<Project>>();
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
} else {
|
||||
@@ -502,7 +476,7 @@ pub async fn user_notifications(
|
||||
crate::database::models::notification_item::Notification::get_many_user(id, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(convert_notification)
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
notifications.sort_by(|a, b| b.created.cmp(&a.created));
|
||||
|
||||
@@ -34,10 +34,10 @@ pub async fn get_mods(
|
||||
.try_collect::<Vec<database::models::ProjectId>>()
|
||||
.await?;
|
||||
|
||||
let projects: Vec<Project> = database::Project::get_many_full(project_ids, &**pool)
|
||||
let projects: Vec<_> = database::Project::get_many_full(project_ids, &**pool)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(crate::routes::projects::convert_project)
|
||||
.map(Project::from)
|
||||
.collect();
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::projects::SearchRequest;
|
||||
use crate::routes::project_creation::{project_create_inner, undo_uploads, CreateError};
|
||||
use crate::routes::projects::{convert_project, ProjectIds};
|
||||
use crate::routes::projects::ProjectIds;
|
||||
use crate::routes::ApiError;
|
||||
use crate::search::{search_for_project, SearchConfig, SearchError};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::auth::{get_user_from_headers, is_authorized};
|
||||
use crate::{database, models};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::web;
|
||||
@@ -98,37 +98,14 @@ pub async fn mods_get(
|
||||
|
||||
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
|
||||
|
||||
let mut projects = Vec::new();
|
||||
let mut projects = Vec::with_capacity(projects_data.len());
|
||||
|
||||
for project_data in projects_data {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if authorized {
|
||||
projects.push(convert_project(project_data));
|
||||
// can't use `map` and `collect` here since `is_authorized` must be async
|
||||
for proj in projects_data {
|
||||
if is_authorized(&proj, &user_option, &pool).await? {
|
||||
projects.push(crate::models::projects::Project::from(proj))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::file_hosting::FileHost;
|
||||
use crate::models::ids::{ProjectId, UserId, VersionId};
|
||||
use crate::models::projects::{Dependency, GameVersion, Loader, Version, VersionFile, VersionType};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::routes::versions::{convert_version, VersionIds, VersionListFilters};
|
||||
use crate::routes::versions::{VersionIds, VersionListFilters};
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::{database, models, Pepper};
|
||||
@@ -91,7 +91,7 @@ pub async fn version_list(
|
||||
.map(|featured| featured == version.featured)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(convert_version)
|
||||
.map(Version::from)
|
||||
.map(convert_to_legacy)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -118,16 +118,14 @@ pub async fn version_list(
|
||||
version.game_versions.contains(&filter.0.version)
|
||||
&& version.loaders.contains(&filter.1.loader)
|
||||
})
|
||||
.map(|version| {
|
||||
response.push(convert_to_legacy(convert_version(version.clone())))
|
||||
})
|
||||
.map(|version| response.push(convert_to_legacy(Version::from(version.clone()))))
|
||||
.unwrap_or(());
|
||||
});
|
||||
|
||||
if response.is_empty() {
|
||||
versions
|
||||
.into_iter()
|
||||
.for_each(|version| response.push(convert_to_legacy(convert_version(version))));
|
||||
.for_each(|version| response.push(convert_to_legacy(Version::from(version))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +152,7 @@ pub async fn versions_get(
|
||||
let mut versions = Vec::new();
|
||||
|
||||
for version_data in versions_data {
|
||||
versions.push(convert_to_legacy(convert_version(version_data)));
|
||||
versions.push(convert_to_legacy(Version::from(version_data)));
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(versions))
|
||||
@@ -169,7 +167,7 @@ pub async fn version_get(
|
||||
let version_data = database::models::Version::get_full(id.into(), &**pool).await?;
|
||||
|
||||
if let Some(data) = version_data {
|
||||
Ok(HttpResponse::Ok().json(convert_to_legacy(convert_version(data))))
|
||||
Ok(HttpResponse::Ok().json(convert_to_legacy(Version::from(data))))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
@@ -214,7 +212,7 @@ pub async fn get_version_from_hash(
|
||||
.await?;
|
||||
|
||||
if let Some(data) = version_data {
|
||||
Ok(HttpResponse::Ok().json(super::versions::convert_version(data)))
|
||||
Ok(HttpResponse::Ok().json(crate::models::projects::Version::from(data)))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::models::projects::{
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::routes::project_creation::{CreateError, UploadedFile};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::routes::read_from_field;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use crate::validate::{validate_file, ValidationResult};
|
||||
use actix_multipart::{Field, Multipart};
|
||||
@@ -587,20 +588,10 @@ pub async fn upload_file(
|
||||
let content_type = crate::util::ext::project_file_type(file_extension)
|
||||
.ok_or_else(|| CreateError::InvalidFileType(file_extension.to_string()))?;
|
||||
|
||||
let mut data = Vec::new();
|
||||
while let Some(chunk) = field.next().await {
|
||||
// Project file size limit of 100MiB
|
||||
const FILE_SIZE_CAP: usize = 100 * (1 << 20);
|
||||
|
||||
if data.len() >= FILE_SIZE_CAP {
|
||||
return Err(CreateError::InvalidInput(
|
||||
String::from("Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files.")
|
||||
));
|
||||
} else {
|
||||
let bytes = chunk.map_err(CreateError::MultipartError)?;
|
||||
data.append(&mut bytes.to_vec());
|
||||
}
|
||||
}
|
||||
let data = read_from_field(
|
||||
field, 100 * (1 << 20),
|
||||
"Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files."
|
||||
).await?;
|
||||
|
||||
let hash = sha1::Sha1::from(&data).hexdigest();
|
||||
let exists = sqlx::query!(
|
||||
@@ -623,7 +614,7 @@ pub async fn upload_file(
|
||||
}
|
||||
|
||||
let validation_result = validate_file(
|
||||
data.as_slice(),
|
||||
&data,
|
||||
file_extension,
|
||||
project_type,
|
||||
loaders,
|
||||
@@ -638,7 +629,7 @@ pub async fn upload_file(
|
||||
"data/{}/versions/{}/{}",
|
||||
project_id, version_number, file_name
|
||||
),
|
||||
data.to_vec(),
|
||||
data.freeze(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use super::ApiError;
|
||||
use crate::database::models::version_item::QueryVersion;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::projects::{GameVersion, Loader};
|
||||
use crate::models::projects::{GameVersion, Loader, Version};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::routes::ok_or_not_found;
|
||||
use crate::{database, Pepper};
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -51,7 +53,7 @@ pub async fn get_version_from_hash(
|
||||
.await?;
|
||||
|
||||
if let Some(data) = version_data {
|
||||
Ok(HttpResponse::Ok().json(super::versions::convert_version(data)))
|
||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(data)))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
@@ -361,11 +363,7 @@ pub async fn get_update_from_hash(
|
||||
if let Some(version_id) = version_ids.last() {
|
||||
let version_data = database::models::Version::get_full(*version_id, &**pool).await?;
|
||||
|
||||
if let Some(data) = version_data {
|
||||
Ok(HttpResponse::Ok().json(super::versions::convert_version(data)))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
ok_or_not_found::<QueryVersion, Version>(version_data)
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
@@ -414,14 +412,16 @@ pub async fn get_versions_from_hashes(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut response = HashMap::new();
|
||||
|
||||
for row in result {
|
||||
if let Some(version) = versions_data.iter().find(|x| x.id.0 == row.version_id) {
|
||||
response.insert(row.hash, super::versions::convert_version(version.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let response: Vec<_> = result
|
||||
.into_iter()
|
||||
.filter_map(|row| {
|
||||
versions_data
|
||||
.clone()
|
||||
.into_iter()
|
||||
.find(|x| x.id.0 == row.version_id)
|
||||
.map(|v| (row.hash, crate::models::projects::Version::from(v)))
|
||||
})
|
||||
.collect();
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ pub async fn update_files(
|
||||
if let Some(version) = versions.iter().find(|x| x.id.0 == row.version_id) {
|
||||
response.insert(
|
||||
row.hash.clone(),
|
||||
super::versions::convert_version(version.clone()),
|
||||
models::projects::Version::from(version.clone()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::ApiError;
|
||||
use crate::database;
|
||||
use crate::models;
|
||||
use crate::models::projects::{Dependency, DependencyType};
|
||||
use crate::models::projects::{Dependency, Version};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
@@ -55,7 +55,7 @@ pub async fn version_list(
|
||||
.map(|featured| featured == version.featured)
|
||||
.unwrap_or(true)
|
||||
})
|
||||
.map(convert_version)
|
||||
.map(Version::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
versions.sort_by(|a, b| b.date_published.cmp(&a.date_published));
|
||||
@@ -83,14 +83,14 @@ pub async fn version_list(
|
||||
version.game_versions.contains(&filter.0.version)
|
||||
&& version.loaders.contains(&filter.1.loader)
|
||||
})
|
||||
.map(|version| response.push(convert_version(version.clone())))
|
||||
.map(|version| response.push(Version::from(version.clone())))
|
||||
.unwrap_or(());
|
||||
});
|
||||
|
||||
if response.is_empty() {
|
||||
versions
|
||||
.into_iter()
|
||||
.for_each(|version| response.push(convert_version(version)));
|
||||
.for_each(|version| response.push(Version::from(version)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,12 +119,10 @@ pub async fn versions_get(
|
||||
.collect();
|
||||
let versions_data = database::models::Version::get_many_full(version_ids, &**pool).await?;
|
||||
|
||||
let mut versions = Vec::new();
|
||||
|
||||
for version_data in versions_data {
|
||||
versions.push(convert_version(version_data));
|
||||
}
|
||||
|
||||
let versions = versions_data
|
||||
.into_iter()
|
||||
.map(Version::from)
|
||||
.collect::<Vec<_>>();
|
||||
Ok(HttpResponse::Ok().json(versions))
|
||||
}
|
||||
|
||||
@@ -137,77 +135,12 @@ pub async fn version_get(
|
||||
let version_data = database::models::Version::get_full(id.into(), &**pool).await?;
|
||||
|
||||
if let Some(data) = version_data {
|
||||
Ok(HttpResponse::Ok().json(convert_version(data)))
|
||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(data)))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_version(
|
||||
data: database::models::version_item::QueryVersion,
|
||||
) -> models::projects::Version {
|
||||
use models::projects::VersionType;
|
||||
|
||||
models::projects::Version {
|
||||
id: data.id.into(),
|
||||
project_id: data.project_id.into(),
|
||||
author_id: data.author_id.into(),
|
||||
|
||||
featured: data.featured,
|
||||
name: data.name,
|
||||
version_number: data.version_number,
|
||||
changelog: data.changelog,
|
||||
changelog_url: data.changelog_url,
|
||||
date_published: data.date_published,
|
||||
downloads: data.downloads as u32,
|
||||
version_type: match data.version_type.as_str() {
|
||||
"release" => VersionType::Release,
|
||||
"beta" => VersionType::Beta,
|
||||
"alpha" => VersionType::Alpha,
|
||||
_ => VersionType::Release,
|
||||
},
|
||||
|
||||
files: data
|
||||
.files
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
models::projects::VersionFile {
|
||||
url: f.url,
|
||||
filename: f.filename,
|
||||
// FIXME: Hashes are currently stored as an ascii byte slice instead
|
||||
// of as an actual byte array in the database
|
||||
hashes: f
|
||||
.hashes
|
||||
.into_iter()
|
||||
.map(|(k, v)| Some((k, String::from_utf8(v).ok()?)))
|
||||
.collect::<Option<_>>()
|
||||
.unwrap_or_else(Default::default),
|
||||
primary: f.primary,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
dependencies: data
|
||||
.dependencies
|
||||
.into_iter()
|
||||
.map(|d| Dependency {
|
||||
version_id: d.version_id.map(|x| x.into()),
|
||||
project_id: d.project_id.map(|x| x.into()),
|
||||
dependency_type: DependencyType::from_str(&*d.dependency_type),
|
||||
})
|
||||
.collect(),
|
||||
game_versions: data
|
||||
.game_versions
|
||||
.into_iter()
|
||||
.map(models::projects::GameVersion)
|
||||
.collect(),
|
||||
loaders: data
|
||||
.loaders
|
||||
.into_iter()
|
||||
.map(models::projects::Loader)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
pub struct EditVersion {
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
|
||||
Reference in New Issue
Block a user