You've already forked AstralRinth
forked from didirus/AstralRinth
Add fields to gallery items (#234)
This commit is contained in:
6
migrations/20210805044459_more_gallery_info.sql
Normal file
6
migrations/20210805044459_more_gallery_info.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE mods_gallery
|
||||||
|
ADD COLUMN title varchar(255),
|
||||||
|
ADD COLUMN description varchar(2048),
|
||||||
|
ADD COLUMN created timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
||||||
|
|
||||||
|
|
||||||
954
sqlx-data.json
954
sqlx-data.json
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
use super::ids::*;
|
use super::ids::*;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DonationUrl {
|
pub struct DonationUrl {
|
||||||
@@ -39,6 +40,9 @@ pub struct GalleryItem {
|
|||||||
pub project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
pub image_url: String,
|
pub image_url: String,
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub created: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GalleryItem {
|
impl GalleryItem {
|
||||||
@@ -49,15 +53,17 @@ impl GalleryItem {
|
|||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT INTO mods_gallery (
|
INSERT INTO mods_gallery (
|
||||||
mod_id, image_url, featured
|
mod_id, image_url, featured, title, description
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3
|
$1, $2, $3, $4, $5
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.project_id as ProjectId,
|
self.project_id as ProjectId,
|
||||||
self.image_url,
|
self.image_url,
|
||||||
self.featured
|
self.featured,
|
||||||
|
self.title,
|
||||||
|
self.description
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -593,7 +599,8 @@ impl Project {
|
|||||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
||||||
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured, ' ,') gallery,
|
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions,
|
||||||
|
STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured || ', ' || COALESCE(mg.title, ' ') || ', ' || COALESCE(mg.description, ' ') || ', ' || mg.created, ' ,') gallery,
|
||||||
STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations
|
STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations
|
||||||
FROM mods m
|
FROM mods m
|
||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||||
@@ -684,11 +691,24 @@ impl Project {
|
|||||||
.map(|d| {
|
.map(|d| {
|
||||||
let strings: Vec<&str> = d.split(", ").collect();
|
let strings: Vec<&str> = d.split(", ").collect();
|
||||||
|
|
||||||
if strings.len() >= 2 {
|
if strings.len() >= 5 {
|
||||||
Some(GalleryItem {
|
Some(GalleryItem {
|
||||||
project_id: id,
|
project_id: id,
|
||||||
image_url: strings[0].to_string(),
|
image_url: strings[0].to_string(),
|
||||||
featured: strings[1].parse().unwrap_or(false),
|
featured: strings[1].parse().unwrap_or(false),
|
||||||
|
title: if strings[2] == " " {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(strings[2].to_string())
|
||||||
|
},
|
||||||
|
description: if strings[3] == " " {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(strings[3].to_string())
|
||||||
|
},
|
||||||
|
created: chrono::DateTime::parse_from_rfc3339(strings[4])
|
||||||
|
.map(|x| x.with_timezone(&chrono::Utc))
|
||||||
|
.unwrap_or_else(|_| chrono::Utc::now()),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -725,7 +745,8 @@ impl Project {
|
|||||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
||||||
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured, ' ,') gallery,
|
STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions,
|
||||||
|
STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured || ', ' || COALESCE(mg.title, ' ') || ', ' || COALESCE(mg.description, ' ') || ', ' || mg.created, ' ,') gallery,
|
||||||
STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations
|
STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations
|
||||||
FROM mods m
|
FROM mods m
|
||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||||
@@ -785,11 +806,14 @@ impl Project {
|
|||||||
.map(|d| {
|
.map(|d| {
|
||||||
let strings: Vec<&str> = d.split(", ").collect();
|
let strings: Vec<&str> = d.split(", ").collect();
|
||||||
|
|
||||||
if strings.len() >= 2 {
|
if strings.len() >= 5 {
|
||||||
Some(GalleryItem {
|
Some(GalleryItem {
|
||||||
project_id: ProjectId(id),
|
project_id: ProjectId(id),
|
||||||
image_url: strings[0].to_string(),
|
image_url: strings[0].to_string(),
|
||||||
featured: strings[1].parse().unwrap_or(false)
|
featured: strings[1].parse().unwrap_or(false),
|
||||||
|
title: if strings[2] == " " { None } else { Some(strings[2].to_string()) },
|
||||||
|
description: if strings[3] == " " { None } else { Some(strings[3].to_string()) },
|
||||||
|
created: chrono::DateTime::parse_from_rfc3339(strings[4]).map(|x| x.with_timezone(&chrono::Utc)).unwrap_or_else(|_| chrono::Utc::now())
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ pub struct Project {
|
|||||||
pub struct GalleryItem {
|
pub struct GalleryItem {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub created: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ struct ProjectCreateData {
|
|||||||
pub license_id: String,
|
pub license_id: String,
|
||||||
|
|
||||||
#[validate(length(max = 64))]
|
#[validate(length(max = 64))]
|
||||||
|
#[validate]
|
||||||
/// The multipart names of the gallery items to upload
|
/// The multipart names of the gallery items to upload
|
||||||
pub gallery_items: Option<Vec<NewGalleryItem>>,
|
pub gallery_items: Option<Vec<NewGalleryItem>>,
|
||||||
}
|
}
|
||||||
@@ -194,6 +195,12 @@ pub struct NewGalleryItem {
|
|||||||
pub item: String,
|
pub item: String,
|
||||||
/// Whether the gallery item should show in search or not
|
/// Whether the gallery item should show in search or not
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
|
#[validate(url, length(min = 1, max = 2048))]
|
||||||
|
/// The title of the gallery item
|
||||||
|
pub title: Option<String>,
|
||||||
|
#[validate(url, length(min = 1, max = 2048))]
|
||||||
|
/// The description of the gallery item
|
||||||
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UploadedFile {
|
pub struct UploadedFile {
|
||||||
@@ -468,6 +475,9 @@ pub async fn project_create_inner(
|
|||||||
gallery_urls.push(crate::models::projects::GalleryItem {
|
gallery_urls.push(crate::models::projects::GalleryItem {
|
||||||
url,
|
url,
|
||||||
featured: item.featured,
|
featured: item.featured,
|
||||||
|
title: item.title.clone(),
|
||||||
|
description: item.description.clone(),
|
||||||
|
created: chrono::Utc::now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -637,6 +647,9 @@ pub async fn project_create_inner(
|
|||||||
project_id: project_id.into(),
|
project_id: project_id.into(),
|
||||||
image_url: x.url.clone(),
|
image_url: x.url.clone(),
|
||||||
featured: x.featured,
|
featured: x.featured,
|
||||||
|
title: x.title.clone(),
|
||||||
|
description: x.description.clone(),
|
||||||
|
created: x.created,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -259,6 +259,9 @@ pub fn convert_project(
|
|||||||
.map(|x| GalleryItem {
|
.map(|x| GalleryItem {
|
||||||
url: x.image_url,
|
url: x.image_url,
|
||||||
featured: x.featured,
|
featured: x.featured,
|
||||||
|
title: x.title,
|
||||||
|
description: x.description,
|
||||||
|
created: x.created,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
@@ -1070,9 +1073,13 @@ pub async fn delete_project_icon(
|
|||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Validate)]
|
||||||
pub struct GalleryCreateQuery {
|
pub struct GalleryCreateQuery {
|
||||||
pub featured: bool,
|
pub featured: bool,
|
||||||
|
#[validate(url, length(min = 1, max = 255))]
|
||||||
|
pub title: Option<String>,
|
||||||
|
#[validate(url, length(min = 1, max = 2048))]
|
||||||
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("{id}/gallery")]
|
#[post("{id}/gallery")]
|
||||||
@@ -1086,6 +1093,9 @@ pub async fn add_gallery_item(
|
|||||||
mut payload: web::Payload,
|
mut payload: web::Payload,
|
||||||
) -> Result<HttpResponse, ApiError> {
|
) -> Result<HttpResponse, ApiError> {
|
||||||
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
|
if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) {
|
||||||
|
item.validate()
|
||||||
|
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let cdn_url = dotenv::var("CDN_URL")?;
|
let cdn_url = dotenv::var("CDN_URL")?;
|
||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
@@ -1147,10 +1157,15 @@ pub async fn add_gallery_item(
|
|||||||
project_id: project_item.id,
|
project_id: project_item.id,
|
||||||
image_url: format!("{}/{}", cdn_url, url),
|
image_url: format!("{}/{}", cdn_url, url),
|
||||||
featured: item.featured,
|
featured: item.featured,
|
||||||
|
title: item.title,
|
||||||
|
description: item.description,
|
||||||
|
created: chrono::Utc::now(),
|
||||||
}
|
}
|
||||||
.insert(&mut transaction)
|
.insert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::InvalidInputError(format!(
|
Err(ApiError::InvalidInputError(format!(
|
||||||
@@ -1160,10 +1175,25 @@ pub async fn add_gallery_item(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Validate)]
|
||||||
pub struct GalleryEditQuery {
|
pub struct GalleryEditQuery {
|
||||||
|
/// The url of the gallery item to edit
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub featured: bool,
|
pub featured: Option<bool>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
with = "::serde_with::rust::double_option"
|
||||||
|
)]
|
||||||
|
#[validate(url, length(min = 1, max = 255))]
|
||||||
|
pub title: Option<Option<String>>,
|
||||||
|
#[serde(
|
||||||
|
default,
|
||||||
|
skip_serializing_if = "Option::is_none",
|
||||||
|
with = "::serde_with::rust::double_option"
|
||||||
|
)]
|
||||||
|
#[validate(url, length(min = 1, max = 2048))]
|
||||||
|
pub description: Option<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[patch("{id}/gallery")]
|
#[patch("{id}/gallery")]
|
||||||
@@ -1176,6 +1206,9 @@ pub async fn edit_gallery_item(
|
|||||||
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
let user = get_user_from_headers(req.headers(), &**pool).await?;
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
|
|
||||||
|
item.validate()
|
||||||
|
.map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?;
|
||||||
|
|
||||||
let project_item =
|
let project_item =
|
||||||
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool)
|
||||||
.await?
|
.await?
|
||||||
@@ -1222,17 +1255,45 @@ pub async fn edit_gallery_item(
|
|||||||
|
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
sqlx::query!(
|
if let Some(featured) = item.featured {
|
||||||
"
|
sqlx::query!(
|
||||||
UPDATE mods_gallery
|
"
|
||||||
SET featured = $2
|
UPDATE mods_gallery
|
||||||
WHERE id = $1
|
SET featured = $2
|
||||||
",
|
WHERE id = $1
|
||||||
id,
|
",
|
||||||
item.featured
|
id,
|
||||||
)
|
featured
|
||||||
.execute(&mut *transaction)
|
)
|
||||||
.await?;
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(title) = item.title {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE mods_gallery
|
||||||
|
SET title = $2
|
||||||
|
WHERE id = $1
|
||||||
|
",
|
||||||
|
id,
|
||||||
|
title
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(description) = item.description {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE mods_gallery
|
||||||
|
SET description = $2
|
||||||
|
WHERE id = $1
|
||||||
|
",
|
||||||
|
id,
|
||||||
|
description
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user