You've already forked AstralRinth
forked from didirus/AstralRinth
More project data (#406)
* More project data * Array_agg fixes + cleanup * fix prepare * Add approval dates to search * Update migrations/20220725204351_more-project-data.sql Co-authored-by: wafflecoffee <emmaffle@modrinth.com> * Add category labels + display categories Co-authored-by: wafflecoffee <emmaffle@modrinth.com>
This commit is contained in:
@@ -21,10 +21,10 @@ use crate::util::auth::get_github_user_from_token;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{scope, Data, Query, ServiceConfig};
|
||||
use actix_web::{get, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn config(cfg: &mut ServiceConfig) {
|
||||
cfg.service(scope("auth").service(auth_callback).service(init));
|
||||
@@ -176,9 +176,9 @@ pub async fn auth_callback(
|
||||
.await?;
|
||||
|
||||
if let Some(result) = result_option {
|
||||
let duration = result.expires - OffsetDateTime::now_utc();
|
||||
let duration: chrono::Duration = result.expires - Utc::now();
|
||||
|
||||
if duration.whole_seconds() < 0 {
|
||||
if duration.num_seconds() < 0 {
|
||||
return Err(AuthorizationError::InvalidCredentials);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ pub async fn auth_callback(
|
||||
email: user.email,
|
||||
avatar_url: Some(user.avatar_url),
|
||||
bio: user.bio,
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
role: Role::Developer.to_string(),
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
|
||||
@@ -108,7 +108,7 @@ pub async fn maven_metadata(
|
||||
.map(|x| x.version_number.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
last_updated: data.inner.updated.format("%Y%m%d%H%M%S"),
|
||||
last_updated: data.inner.updated.format("%Y%m%d%H%M%S").to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -14,13 +14,13 @@ use actix_multipart::{Field, Multipart};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::stream::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@@ -166,6 +166,9 @@ struct ProjectCreateData {
|
||||
#[validate(length(max = 3))]
|
||||
/// A list of the categories that the project is in.
|
||||
pub categories: Vec<String>,
|
||||
#[validate(length(max = 256))]
|
||||
/// A list of the categories that the project is in.
|
||||
pub additional_categories: Vec<String>,
|
||||
|
||||
#[validate(url, length(max = 2048))]
|
||||
/// An optional link to where to submit bugs or issues with the project.
|
||||
@@ -388,7 +391,7 @@ pub async fn project_create_inner(
|
||||
{
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(SELECT 1 FROM mods WHERE slug = $1)
|
||||
SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1))
|
||||
",
|
||||
create_data.slug
|
||||
)
|
||||
@@ -522,7 +525,7 @@ pub async fn project_create_inner(
|
||||
featured: item.featured,
|
||||
title: item.title.clone(),
|
||||
description: item.description.clone(),
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
});
|
||||
|
||||
continue;
|
||||
@@ -593,6 +596,19 @@ pub async fn project_create_inner(
|
||||
categories.push(id);
|
||||
}
|
||||
|
||||
let mut additional_categories =
|
||||
Vec::with_capacity(project_create_data.additional_categories.len());
|
||||
for category in &project_create_data.additional_categories {
|
||||
let id = models::categories::Category::get_id_project(
|
||||
category,
|
||||
project_type_id,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| CreateError::InvalidCategory(category.clone()))?;
|
||||
additional_categories.push(id);
|
||||
}
|
||||
|
||||
let team = models::team_item::TeamBuilder {
|
||||
members: vec![models::team_item::TeamMemberBuilder {
|
||||
user_id: current_user.id.into(),
|
||||
@@ -698,6 +714,7 @@ pub async fn project_create_inner(
|
||||
license_url: project_create_data.license_url,
|
||||
discord_url: project_create_data.discord_url,
|
||||
categories,
|
||||
additional_categories,
|
||||
initial_versions: versions,
|
||||
status: status_id,
|
||||
client_side: client_side_id,
|
||||
@@ -718,7 +735,7 @@ pub async fn project_create_inner(
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let now = Utc::now();
|
||||
|
||||
let response = crate::models::projects::Project {
|
||||
id: project_id,
|
||||
@@ -731,6 +748,7 @@ pub async fn project_create_inner(
|
||||
body_url: None,
|
||||
published: now,
|
||||
updated: now,
|
||||
approved: None,
|
||||
status: status.clone(),
|
||||
moderator_message: None,
|
||||
license: License {
|
||||
@@ -743,6 +761,7 @@ pub async fn project_create_inner(
|
||||
downloads: 0,
|
||||
followers: 0,
|
||||
categories: project_create_data.categories,
|
||||
additional_categories: project_create_data.additional_categories,
|
||||
versions: project_builder
|
||||
.initial_versions
|
||||
.iter()
|
||||
|
||||
@@ -11,12 +11,12 @@ 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::{delete, get, patch, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::{PgPool, Row};
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use validator::Validate;
|
||||
|
||||
#[get("search")]
|
||||
@@ -112,7 +112,7 @@ pub async fn project_get_check(
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT id FROM mods
|
||||
WHERE LOWER(slug) = LOWER($1)
|
||||
WHERE slug = LOWER($1)
|
||||
",
|
||||
&slug
|
||||
)
|
||||
@@ -126,7 +126,7 @@ pub async fn project_get_check(
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT id FROM mods
|
||||
WHERE LOWER(slug) = LOWER($1)
|
||||
WHERE slug = LOWER($1)
|
||||
",
|
||||
&slug
|
||||
)
|
||||
@@ -259,6 +259,8 @@ pub struct EditProject {
|
||||
pub body: Option<String>,
|
||||
#[validate(length(max = 3))]
|
||||
pub categories: Option<Vec<String>>,
|
||||
#[validate(length(max = 256))]
|
||||
pub additional_categories: Option<Vec<String>>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
@@ -483,6 +485,21 @@ pub async fn project_edit(
|
||||
)
|
||||
})?;
|
||||
|
||||
if status == &ProjectStatus::Approved
|
||||
|| status == &ProjectStatus::Unlisted
|
||||
{
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET published = NOW()
|
||||
WHERE id = $1 AND approved = NULL
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
@@ -513,7 +530,7 @@ pub async fn project_edit(
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM mods_categories
|
||||
WHERE joining_mod_id = $1
|
||||
WHERE joining_mod_id = $1 AND is_additional = FALSE
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
@@ -536,8 +553,8 @@ pub async fn project_edit(
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO mods_categories (joining_mod_id, joining_category_id)
|
||||
VALUES ($1, $2)
|
||||
INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)
|
||||
VALUES ($1, $2, FALSE)
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
category_id as database::models::ids::CategoryId,
|
||||
@@ -547,6 +564,51 @@ pub async fn project_edit(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(categories) = &new_project.additional_categories {
|
||||
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the additional categories of this project!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM mods_categories
|
||||
WHERE joining_mod_id = $1 AND is_additional = TRUE
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
for category in categories {
|
||||
let category_id =
|
||||
database::models::categories::Category::get_id(
|
||||
category,
|
||||
&mut *transaction,
|
||||
)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::InvalidInput(format!(
|
||||
"Category {} does not exist.",
|
||||
category.clone()
|
||||
))
|
||||
})?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)
|
||||
VALUES ($1, $2, TRUE)
|
||||
",
|
||||
id as database::models::ids::ProjectId,
|
||||
category_id as database::models::ids::CategoryId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(issues_url) = &new_project.issues_url {
|
||||
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
@@ -1178,7 +1240,7 @@ pub async fn add_gallery_item(
|
||||
featured: item.featured,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
@@ -5,10 +5,10 @@ use crate::util::auth::{
|
||||
check_is_moderator_from_headers, get_user_from_headers,
|
||||
};
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::StreamExt;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateReport {
|
||||
@@ -60,7 +60,7 @@ pub async fn report_create(
|
||||
user_id: None,
|
||||
body: new_report.body.clone(),
|
||||
reporter: current_user.id.into(),
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
};
|
||||
|
||||
match new_report.item_type {
|
||||
@@ -109,7 +109,7 @@ pub async fn report_create(
|
||||
item_type: new_report.item_type.clone(),
|
||||
reporter: current_user.id,
|
||||
body: new_report.body.clone(),
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ use crate::database::models::categories::{
|
||||
};
|
||||
use crate::util::auth::check_is_admin_from_headers;
|
||||
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use models::categories::{Category, GameVersion, Loader};
|
||||
use sqlx::PgPool;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
@@ -38,6 +38,7 @@ pub struct CategoryData {
|
||||
icon: String,
|
||||
name: String,
|
||||
project_type: String,
|
||||
header: String,
|
||||
}
|
||||
|
||||
// TODO: searching / filtering? Could be used to implement a live
|
||||
@@ -53,6 +54,7 @@ pub async fn category_list(
|
||||
icon: x.icon,
|
||||
name: x.category,
|
||||
project_type: x.project_type,
|
||||
header: x.header,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -84,6 +86,7 @@ pub async fn category_create(
|
||||
.name(&new_category.name)?
|
||||
.project_type(&project_type)?
|
||||
.icon(&new_category.icon)?
|
||||
.header(&new_category.header)?
|
||||
.insert(&**pool)
|
||||
.await?;
|
||||
|
||||
@@ -202,8 +205,7 @@ pub async fn loader_delete(
|
||||
pub struct GameVersionQueryData {
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
#[serde(with = "crate::util::time_ser")]
|
||||
pub date: OffsetDateTime,
|
||||
pub date: DateTime<Utc>,
|
||||
pub major: bool,
|
||||
}
|
||||
|
||||
@@ -243,7 +245,7 @@ pub async fn game_version_list(
|
||||
pub struct GameVersionData {
|
||||
#[serde(rename = "type")]
|
||||
type_: String,
|
||||
date: Option<OffsetDateTime>,
|
||||
date: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[put("game_version/{name}")]
|
||||
|
||||
@@ -7,10 +7,10 @@ use crate::util::auth::{
|
||||
};
|
||||
use actix_web::web;
|
||||
use actix_web::{get, post, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Report {
|
||||
@@ -20,8 +20,7 @@ pub struct Report {
|
||||
pub item_type: ItemType,
|
||||
pub reporter: UserId,
|
||||
pub body: String,
|
||||
#[serde(with = "crate::util::time_ser")]
|
||||
pub created: OffsetDateTime,
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@@ -93,7 +92,7 @@ pub async fn report_create(
|
||||
user_id: None,
|
||||
body: new_report.body.clone(),
|
||||
reporter: current_user.id.into(),
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
};
|
||||
|
||||
match new_report.item_type {
|
||||
@@ -142,7 +141,7 @@ pub async fn report_create(
|
||||
item_type: new_report.item_type.clone(),
|
||||
reporter: current_user.id,
|
||||
body: new_report.body.clone(),
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::routes::ApiError;
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::{database, models};
|
||||
use actix_web::{delete, get, web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
/// A specific version of a mod
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -25,8 +25,7 @@ pub struct LegacyVersion {
|
||||
pub version_number: String,
|
||||
pub changelog: String,
|
||||
pub changelog_url: Option<String>,
|
||||
#[serde(with = "crate::util::time_ser")]
|
||||
pub date_published: OffsetDateTime,
|
||||
pub date_published: DateTime<Utc>,
|
||||
pub downloads: u32,
|
||||
pub version_type: VersionType,
|
||||
pub files: Vec<VersionFile>,
|
||||
|
||||
@@ -18,10 +18,10 @@ use crate::validate::{validate_file, ValidationResult};
|
||||
use actix_multipart::{Field, Multipart};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::stream::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use time::OffsetDateTime;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate, Clone)]
|
||||
@@ -402,7 +402,7 @@ async fn version_create_inner(
|
||||
version_number: builder.version_number.clone(),
|
||||
changelog: builder.changelog.clone(),
|
||||
changelog_url: None,
|
||||
date_published: OffsetDateTime::now_utc(),
|
||||
date_published: Utc::now(),
|
||||
downloads: 0,
|
||||
version_type: version_data.release_channel,
|
||||
files: builder
|
||||
@@ -722,23 +722,18 @@ pub async fn upload_file(
|
||||
|
||||
for file in &format.files {
|
||||
if let Some(dep) = res.iter().find(|x| {
|
||||
x.hash.as_deref()
|
||||
Some(&*x.hash)
|
||||
== file
|
||||
.hashes
|
||||
.get(&PackFileHash::Sha1)
|
||||
.map(|x| x.as_bytes())
|
||||
}) {
|
||||
if let Some(project_id) = dep.project_id {
|
||||
if let Some(version_id) = dep.version_id {
|
||||
dependencies.push(DependencyBuilder {
|
||||
project_id: Some(models::ProjectId(project_id)),
|
||||
version_id: Some(models::VersionId(version_id)),
|
||||
file_name: None,
|
||||
dependency_type: DependencyType::Embedded
|
||||
.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
dependencies.push(DependencyBuilder {
|
||||
project_id: Some(models::ProjectId(dep.project_id)),
|
||||
version_id: Some(models::VersionId(dep.version_id)),
|
||||
file_name: None,
|
||||
dependency_type: DependencyType::Embedded.to_string(),
|
||||
});
|
||||
} else if let Some(first_download) = file.downloads.first() {
|
||||
dependencies.push(DependencyBuilder {
|
||||
project_id: None,
|
||||
|
||||
Reference in New Issue
Block a user