You've already forked AstralRinth
forked from didirus/AstralRinth
Update file restrictions, download counting, project colors, etc (#515)
* Update file restrictions, download counting, project colors, etc * Run fmt + clippy + prepare
This commit is contained in:
@@ -34,9 +34,24 @@ pub async fn count_download(
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
|
||||
let (version_id, project_id) = if let Some(version) = sqlx::query!(
|
||||
"SELECT id, mod_id FROM versions
|
||||
WHERE ((version_number = $1 OR id = $3) AND mod_id = $2)",
|
||||
let (version_id, project_id, file_type) = if let Some(version) =
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT v.id id, v.mod_id mod_id, file_type FROM files f
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE f.url = $1
|
||||
",
|
||||
download_body.url,
|
||||
)
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
{
|
||||
(version.id, version.mod_id, version.file_type)
|
||||
} else if let Some(version) = sqlx::query!(
|
||||
"
|
||||
SELECT id, mod_id FROM versions
|
||||
WHERE ((version_number = $1 OR id = $3) AND mod_id = $2)
|
||||
",
|
||||
download_body.version_name,
|
||||
project_id as crate::database::models::ids::ProjectId,
|
||||
id_option
|
||||
@@ -44,31 +59,21 @@ pub async fn count_download(
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
{
|
||||
(version.id, version.mod_id)
|
||||
} else if let Some(version) = sqlx::query!(
|
||||
"
|
||||
SELECT v.id id, v.mod_id project_id FROM files f
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE f.url = $1
|
||||
",
|
||||
download_body.url,
|
||||
)
|
||||
.fetch_optional(pool.as_ref())
|
||||
.await?
|
||||
{
|
||||
(version.id, version.project_id)
|
||||
(version.id, version.mod_id, None)
|
||||
} else {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"Specified version does not exist!".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
download_queue
|
||||
.add(
|
||||
crate::database::models::ProjectId(project_id),
|
||||
crate::database::models::VersionId(version_id),
|
||||
)
|
||||
.await;
|
||||
if file_type.is_none() {
|
||||
download_queue
|
||||
.add(
|
||||
crate::database::models::ProjectId(project_id),
|
||||
crate::database::models::VersionId(version_id),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
|
||||
@@ -677,10 +677,13 @@ pub async fn upload_file(
|
||||
let exists = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM hashes h
|
||||
WHERE h.algorithm = $2 AND h.hash = $1)
|
||||
INNER JOIN files f ON f.id = h.file_id
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE h.algorithm = $2 AND h.hash = $1 AND v.mod_id != $3)
|
||||
",
|
||||
hash.as_bytes(),
|
||||
"sha1"
|
||||
"sha1",
|
||||
project_id.0 as i64
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
use super::ApiError;
|
||||
use crate::database::models::{version_item::QueryVersion, DatabaseError};
|
||||
use crate::models::ids::VersionId;
|
||||
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, models};
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::collections::HashMap;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Algorithm {
|
||||
pub struct HashQuery {
|
||||
#[serde(default = "default_algorithm")]
|
||||
pub algorithm: String,
|
||||
#[serde(default = "default_multiple")]
|
||||
pub multiple: bool,
|
||||
pub version_id: Option<VersionId>,
|
||||
}
|
||||
|
||||
fn default_algorithm() -> String {
|
||||
"sha1".into()
|
||||
}
|
||||
|
||||
fn default_multiple() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
// under /api/v1/version_file/{hash}
|
||||
#[get("{version_id}")]
|
||||
pub async fn get_version_from_hash(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
algorithm: web::Query<Algorithm>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let hash = info.into_inner().0.to_lowercase();
|
||||
|
||||
@@ -38,32 +47,42 @@ pub async fn get_version_from_hash(
|
||||
INNER JOIN versions v on f.version_id = v.id AND v.status != ANY($1)
|
||||
INNER JOIN mods m on v.mod_id = m.id
|
||||
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||
ORDER BY v.date_published ASC
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
hash.as_bytes(),
|
||||
algorithm.algorithm,
|
||||
hash_query.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_optional(&**pool)
|
||||
.fetch_all(&**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(id) = result {
|
||||
let version_data = database::models::Version::get_full(
|
||||
database::models::VersionId(id.version_id),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
let versions_data = database::models::Version::get_many_full(
|
||||
result
|
||||
.iter()
|
||||
.map(|x| database::models::VersionId(x.version_id))
|
||||
.collect(),
|
||||
&**pool,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(data) = version_data {
|
||||
Ok(HttpResponse::Ok().json(models::projects::Version::from(data)))
|
||||
if let Some(first) = versions_data.first() {
|
||||
if hash_query.multiple {
|
||||
Ok(HttpResponse::Ok().json(
|
||||
versions_data
|
||||
.into_iter()
|
||||
.map(models::projects::Version::from)
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
Ok(HttpResponse::Ok()
|
||||
.json(models::projects::Version::from(first.clone())))
|
||||
}
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
@@ -80,7 +99,7 @@ pub struct DownloadRedirect {
|
||||
pub async fn download_version(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
algorithm: web::Query<Algorithm>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let hash = info.into_inner().0.to_lowercase();
|
||||
let mut transaction = pool.begin().await?;
|
||||
@@ -92,10 +111,11 @@ pub async fn download_version(
|
||||
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||
INNER JOIN mods m on v.mod_id = m.id
|
||||
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||
ORDER BY v.date_published ASC
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
hash.as_bytes(),
|
||||
algorithm.algorithm,
|
||||
hash_query.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::<Vec<String>>(),
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
@@ -118,7 +138,7 @@ pub async fn delete_file(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
algorithm: web::Query<Algorithm>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||
|
||||
@@ -130,15 +150,18 @@ pub async fn delete_file(
|
||||
INNER JOIN files f ON h.file_id = f.id
|
||||
INNER JOIN versions v ON v.id = f.version_id
|
||||
WHERE h.algorithm = $2 AND h.hash = $1
|
||||
ORDER BY v.date_published ASC
|
||||
",
|
||||
hash.as_bytes(),
|
||||
algorithm.algorithm
|
||||
hash_query.algorithm
|
||||
)
|
||||
.fetch_optional(&**pool)
|
||||
.await
|
||||
?;
|
||||
.fetch_all(&**pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = result {
|
||||
if let Some(row) = result.iter().find_or_first(|x| {
|
||||
hash_query.version_id.is_none()
|
||||
|| Some(x.version_id) == hash_query.version_id.map(|x| x.0 as i64)
|
||||
}) {
|
||||
if !user.role.is_admin() {
|
||||
let team_member =
|
||||
database::models::TeamMember::get_from_user_id_version(
|
||||
@@ -227,7 +250,7 @@ pub struct UpdateData {
|
||||
pub async fn get_update_from_hash(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
algorithm: web::Query<Algorithm>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
update_data: web::Json<UpdateData>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let hash = info.into_inner().0.to_lowercase();
|
||||
@@ -243,13 +266,14 @@ pub async fn get_update_from_hash(
|
||||
INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)
|
||||
INNER JOIN mods m on v.mod_id = m.id
|
||||
WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)
|
||||
ORDER BY v.date_published ASC
|
||||
",
|
||||
&*crate::models::projects::VersionStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<String>>(),
|
||||
hash.as_bytes(),
|
||||
algorithm.algorithm,
|
||||
hash_query.algorithm,
|
||||
&*crate::models::projects::ProjectStatus::iterator()
|
||||
.filter(|x| x.is_hidden())
|
||||
.map(|x| x.to_string())
|
||||
|
||||
@@ -70,9 +70,6 @@ pub async fn index_local(
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let mut gallery = m.featured_gallery.unwrap_or_default();
|
||||
gallery.append(&mut m.gallery.unwrap_or_default());
|
||||
|
||||
UploadSearchProject {
|
||||
project_id: project_id.to_string(),
|
||||
title: m.title,
|
||||
@@ -93,10 +90,11 @@ pub async fn index_local(
|
||||
server_side: m.server_side_type,
|
||||
slug: m.slug,
|
||||
project_type: m.project_type_name,
|
||||
gallery,
|
||||
gallery: m.gallery.unwrap_or_default(),
|
||||
display_categories,
|
||||
open_source,
|
||||
color: m.color.map(|x| x as u32),
|
||||
featured_gallery: m.featured_gallery.unwrap_or_default().first().cloned(),
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -198,6 +198,8 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
|
||||
"client_side",
|
||||
"server_side",
|
||||
"gallery",
|
||||
"featured_gallery",
|
||||
"color",
|
||||
];
|
||||
|
||||
const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] =
|
||||
@@ -218,6 +220,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
||||
"date_modified",
|
||||
"project_id",
|
||||
"open_source",
|
||||
"color",
|
||||
];
|
||||
|
||||
const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
|
||||
|
||||
@@ -89,6 +89,7 @@ pub struct UploadSearchProject {
|
||||
pub client_side: String,
|
||||
pub server_side: String,
|
||||
pub gallery: Vec<String>,
|
||||
pub featured_gallery: Option<String>,
|
||||
/// RFC 3339 formatted creation date of the project
|
||||
pub date_created: DateTime<Utc>,
|
||||
/// Unix timestamp of the creation date of the project
|
||||
@@ -119,7 +120,6 @@ pub struct ResultSearchProject {
|
||||
pub description: String,
|
||||
pub categories: Vec<String>,
|
||||
pub display_categories: Vec<String>,
|
||||
// TODO: more efficient format for listing versions, without many repetitions
|
||||
pub versions: Vec<String>,
|
||||
pub downloads: i32,
|
||||
pub follows: i32,
|
||||
@@ -133,6 +133,7 @@ pub struct ResultSearchProject {
|
||||
pub client_side: String,
|
||||
pub server_side: String,
|
||||
pub gallery: Vec<String>,
|
||||
pub featured_gallery: Option<String>,
|
||||
pub color: Option<u32>,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ use image::imageops::FilterType;
|
||||
use image::{EncodableLayout, ImageError};
|
||||
|
||||
pub fn get_color_from_img(data: &[u8]) -> Result<Option<u32>, ImageError> {
|
||||
let image =
|
||||
image::load_from_memory(data)?.resize(256, 256, FilterType::Nearest);
|
||||
let image = image::load_from_memory(data)?
|
||||
.resize(256, 256, FilterType::Nearest)
|
||||
.crop_imm(128, 128, 64, 64);
|
||||
let color = color_thief::get_palette(
|
||||
image.to_rgb8().as_bytes(),
|
||||
ColorFormat::Rgb,
|
||||
|
||||
Reference in New Issue
Block a user