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:
@@ -1,7 +1,8 @@
|
||||
use super::ids::*;
|
||||
use super::DatabaseError;
|
||||
use chrono::DateTime;
|
||||
use chrono::Utc;
|
||||
use futures::TryStreamExt;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub struct ProjectType {
|
||||
pub id: ProjectTypeId,
|
||||
@@ -20,7 +21,7 @@ pub struct GameVersion {
|
||||
pub id: GameVersionId,
|
||||
pub version: String,
|
||||
pub version_type: String,
|
||||
pub date: OffsetDateTime,
|
||||
pub date: DateTime<Utc>,
|
||||
pub major: bool,
|
||||
}
|
||||
|
||||
@@ -29,6 +30,7 @@ pub struct Category {
|
||||
pub category: String,
|
||||
pub project_type: String,
|
||||
pub icon: String,
|
||||
pub header: String,
|
||||
}
|
||||
|
||||
pub struct ReportType {
|
||||
@@ -52,6 +54,7 @@ pub struct CategoryBuilder<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
pub project_type: Option<&'a ProjectTypeId>,
|
||||
pub icon: Option<&'a str>,
|
||||
pub header: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl Category {
|
||||
@@ -60,6 +63,7 @@ impl Category {
|
||||
name: None,
|
||||
project_type: None,
|
||||
icon: None,
|
||||
header: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +149,7 @@ impl Category {
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT c.id id, c.category category, c.icon icon, pt.name project_type
|
||||
SELECT c.id id, c.category category, c.icon icon, c.header header, pt.name project_type
|
||||
FROM categories c
|
||||
INNER JOIN project_types pt ON c.project_type = pt.id
|
||||
ORDER BY c.id
|
||||
@@ -158,6 +162,7 @@ impl Category {
|
||||
category: c.category,
|
||||
project_type: c.project_type,
|
||||
icon: c.icon,
|
||||
header: c.header
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<Category>>()
|
||||
@@ -211,6 +216,16 @@ impl<'a> CategoryBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header(
|
||||
self,
|
||||
header: &'a str,
|
||||
) -> Result<CategoryBuilder<'a>, DatabaseError> {
|
||||
Ok(Self {
|
||||
header: Some(header),
|
||||
..self
|
||||
})
|
||||
}
|
||||
|
||||
pub fn project_type(
|
||||
self,
|
||||
project_type: &'a ProjectTypeId,
|
||||
@@ -243,13 +258,14 @@ impl<'a> CategoryBuilder<'a> {
|
||||
})?;
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
INSERT INTO categories (category, project_type, icon)
|
||||
VALUES ($1, $2, $3)
|
||||
INSERT INTO categories (category, project_type, icon, header)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id
|
||||
",
|
||||
self.name,
|
||||
id as ProjectTypeId,
|
||||
self.icon
|
||||
self.icon,
|
||||
self.header
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
@@ -327,7 +343,7 @@ impl Loader {
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT l.id id, l.loader loader, l.icon icon,
|
||||
ARRAY_AGG(DISTINCT pt.name) project_types
|
||||
ARRAY_AGG(DISTINCT pt.name) filter (where pt.name is not null) project_types
|
||||
FROM loaders l
|
||||
LEFT OUTER JOIN loaders_project_types lpt ON joining_loader_id = l.id
|
||||
LEFT OUTER JOIN project_types pt ON lpt.joining_project_type_id = pt.id
|
||||
@@ -470,7 +486,7 @@ impl<'a> LoaderBuilder<'a> {
|
||||
pub struct GameVersionBuilder<'a> {
|
||||
pub version: Option<&'a str>,
|
||||
pub version_type: Option<&'a str>,
|
||||
pub date: Option<&'a OffsetDateTime>,
|
||||
pub date: Option<&'a DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl GameVersion {
|
||||
@@ -688,10 +704,7 @@ impl<'a> GameVersionBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn created(
|
||||
self,
|
||||
created: &'a OffsetDateTime,
|
||||
) -> GameVersionBuilder<'a> {
|
||||
pub fn created(self, created: &'a DateTime<Utc>) -> GameVersionBuilder<'a> {
|
||||
Self {
|
||||
date: Some(created),
|
||||
..self
|
||||
@@ -719,7 +732,7 @@ impl<'a> GameVersionBuilder<'a> {
|
||||
",
|
||||
self.version,
|
||||
self.version_type,
|
||||
self.date.map(|x| time::PrimitiveDateTime::new(x.date(), x.time())),
|
||||
self.date.map(chrono::DateTime::naive_utc),
|
||||
)
|
||||
.fetch_one(exec)
|
||||
.await?;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
// TODO: remove attr once routes are created
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub mod categories;
|
||||
pub mod ids;
|
||||
@@ -123,14 +123,12 @@ impl ids::ProjectTypeId {
|
||||
.fetch_optional(exec)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| ids::ProjectTypeId(r.id)))
|
||||
Ok(result.map(|r| ProjectTypeId(r.id)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_postgres_date(input: &str) -> OffsetDateTime {
|
||||
OffsetDateTime::parse(
|
||||
format!("{}:00Z", input.replace(' ', "T")),
|
||||
time::Format::Rfc3339,
|
||||
)
|
||||
.unwrap_or_else(|_| OffsetDateTime::now_utc())
|
||||
pub fn convert_postgres_date(input: &str) -> DateTime<Utc> {
|
||||
DateTime::parse_from_rfc3339(&*format!("{}:00Z", input.replace(' ', "T")))
|
||||
.map(|x| x.with_timezone(&Utc))
|
||||
.unwrap_or_else(|_| Utc::now())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::DatabaseError;
|
||||
use time::OffsetDateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub struct NotificationBuilder {
|
||||
pub notification_type: Option<String>,
|
||||
@@ -23,7 +23,7 @@ pub struct Notification {
|
||||
pub text: String,
|
||||
pub link: String,
|
||||
pub read: bool,
|
||||
pub created: OffsetDateTime,
|
||||
pub created: DateTime<Utc>,
|
||||
pub actions: Vec<NotificationAction>,
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ impl NotificationBuilder {
|
||||
text: self.text.clone(),
|
||||
link: self.link.clone(),
|
||||
read: false,
|
||||
created: OffsetDateTime::now_utc(),
|
||||
created: Utc::now(),
|
||||
actions,
|
||||
}
|
||||
.insert(&mut *transaction)
|
||||
@@ -124,7 +124,7 @@ impl Notification {
|
||||
let result = sqlx::query!(
|
||||
"
|
||||
SELECT n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type,
|
||||
STRING_AGG(DISTINCT na.id || ' |||| ' || na.title || ' |||| ' || na.action_route || ' |||| ' || na.action_route_method, ' ~~~~ ') actions
|
||||
ARRAY_AGG(DISTINCT na.id || ' |||| ' || na.title || ' |||| ' || na.action_route || ' |||| ' || na.action_route_method) filter (where na.id is not null) actions
|
||||
FROM notifications n
|
||||
LEFT OUTER JOIN notifications_actions na on n.id = na.notification_id
|
||||
WHERE n.id = $1
|
||||
@@ -138,24 +138,21 @@ impl Notification {
|
||||
if let Some(row) = result {
|
||||
let mut actions: Vec<NotificationAction> = Vec::new();
|
||||
|
||||
row.actions
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.for_each(|x| {
|
||||
let action: Vec<&str> = x.split(" |||| ").collect();
|
||||
row.actions.unwrap_or_default().into_iter().for_each(|x| {
|
||||
let action: Vec<&str> = x.split(" |||| ").collect();
|
||||
|
||||
if action.len() >= 3 {
|
||||
actions.push(NotificationAction {
|
||||
id: NotificationActionId(
|
||||
action[0].parse().unwrap_or(0),
|
||||
),
|
||||
notification_id: id,
|
||||
title: action[1].to_string(),
|
||||
action_route_method: action[3].to_string(),
|
||||
action_route: action[2].to_string(),
|
||||
});
|
||||
}
|
||||
});
|
||||
if action.len() >= 3 {
|
||||
actions.push(NotificationAction {
|
||||
id: NotificationActionId(
|
||||
action[0].parse().unwrap_or(0),
|
||||
),
|
||||
notification_id: id,
|
||||
title: action[1].to_string(),
|
||||
action_route_method: action[3].to_string(),
|
||||
action_route: action[2].to_string(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Some(Notification {
|
||||
id,
|
||||
@@ -187,7 +184,7 @@ impl Notification {
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT n.id, n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type,
|
||||
STRING_AGG(DISTINCT na.id || ' |||| ' || na.title || ' |||| ' || na.action_route || ' |||| ' || na.action_route_method, ' ~~~~ ') actions
|
||||
ARRAY_AGG(DISTINCT na.id || ' |||| ' || na.title || ' |||| ' || na.action_route || ' |||| ' || na.action_route_method) filter (where na.id is not null) actions
|
||||
FROM notifications n
|
||||
LEFT OUTER JOIN notifications_actions na on n.id = na.notification_id
|
||||
WHERE n.id = ANY($1)
|
||||
@@ -202,7 +199,7 @@ impl Notification {
|
||||
let id = NotificationId(row.id);
|
||||
let mut actions: Vec<NotificationAction> = Vec::new();
|
||||
|
||||
row.actions.unwrap_or_default().split(" ~~~~ ").for_each(|x| {
|
||||
row.actions.unwrap_or_default().into_iter().for_each(|x| {
|
||||
let action: Vec<&str> = x.split(" |||| ").collect();
|
||||
|
||||
if action.len() >= 3 {
|
||||
@@ -245,7 +242,7 @@ impl Notification {
|
||||
sqlx::query!(
|
||||
"
|
||||
SELECT n.id, n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type,
|
||||
STRING_AGG(DISTINCT na.id || ' |||| ' || na.title || ' |||| ' || na.action_route || ' |||| ' || na.action_route_method, ' ~~~~ ') actions
|
||||
ARRAY_AGG(DISTINCT na.id || ' |||| ' || na.title || ' |||| ' || na.action_route || ' |||| ' || na.action_route_method) filter (where na.id is not null) actions
|
||||
FROM notifications n
|
||||
LEFT OUTER JOIN notifications_actions na on n.id = na.notification_id
|
||||
WHERE n.user_id = $1
|
||||
@@ -259,7 +256,7 @@ impl Notification {
|
||||
let id = NotificationId(row.id);
|
||||
let mut actions: Vec<NotificationAction> = Vec::new();
|
||||
|
||||
row.actions.unwrap_or_default().split(" ~~~~ ").for_each(|x| {
|
||||
row.actions.unwrap_or_default().into_iter().for_each(|x| {
|
||||
let action: Vec<&str> = x.split(" |||| ").collect();
|
||||
|
||||
if action.len() >= 3 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::ids::*;
|
||||
use crate::database::models::convert_postgres_date;
|
||||
use time::OffsetDateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DonationUrl {
|
||||
@@ -43,7 +43,7 @@ pub struct GalleryItem {
|
||||
pub featured: bool,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub created: OffsetDateTime,
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl GalleryItem {
|
||||
@@ -87,6 +87,7 @@ pub struct ProjectBuilder {
|
||||
pub license_url: Option<String>,
|
||||
pub discord_url: Option<String>,
|
||||
pub categories: Vec<CategoryId>,
|
||||
pub additional_categories: Vec<CategoryId>,
|
||||
pub initial_versions: Vec<super::version_item::VersionBuilder>,
|
||||
pub status: StatusId,
|
||||
pub client_side: SideTypeId,
|
||||
@@ -110,8 +111,9 @@ impl ProjectBuilder {
|
||||
description: self.description,
|
||||
body: self.body,
|
||||
body_url: None,
|
||||
published: time::OffsetDateTime::now_utc(),
|
||||
updated: time::OffsetDateTime::now_utc(),
|
||||
published: Utc::now(),
|
||||
updated: Utc::now(),
|
||||
approved: None,
|
||||
status: self.status,
|
||||
downloads: 0,
|
||||
follows: 0,
|
||||
@@ -148,8 +150,8 @@ impl ProjectBuilder {
|
||||
for category in self.categories {
|
||||
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)
|
||||
",
|
||||
self.project_id as ProjectId,
|
||||
category as CategoryId,
|
||||
@@ -158,6 +160,19 @@ impl ProjectBuilder {
|
||||
.await?;
|
||||
}
|
||||
|
||||
for category in self.additional_categories {
|
||||
sqlx::query!(
|
||||
"
|
||||
INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)
|
||||
VALUES ($1, $2, TRUE)
|
||||
",
|
||||
self.project_id as ProjectId,
|
||||
category as CategoryId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(self.project_id)
|
||||
}
|
||||
}
|
||||
@@ -170,8 +185,9 @@ pub struct Project {
|
||||
pub description: String,
|
||||
pub body: String,
|
||||
pub body_url: Option<String>,
|
||||
pub published: OffsetDateTime,
|
||||
pub updated: OffsetDateTime,
|
||||
pub published: DateTime<Utc>,
|
||||
pub updated: DateTime<Utc>,
|
||||
pub approved: Option<DateTime<Utc>>,
|
||||
pub status: StatusId,
|
||||
pub downloads: i32,
|
||||
pub follows: i32,
|
||||
@@ -248,7 +264,7 @@ impl Project {
|
||||
"
|
||||
SELECT project_type, title, description, downloads, follows,
|
||||
icon_url, body, body_url, published,
|
||||
updated, status,
|
||||
updated, approved, status,
|
||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||
team_id, client_side, server_side, license, slug,
|
||||
moderation_message, moderation_message_body
|
||||
@@ -286,6 +302,7 @@ impl Project {
|
||||
follows: row.follows,
|
||||
moderation_message: row.moderation_message,
|
||||
moderation_message_body: row.moderation_message_body,
|
||||
approved: row.approved,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -307,7 +324,7 @@ impl Project {
|
||||
"
|
||||
SELECT id, project_type, title, description, downloads, follows,
|
||||
icon_url, body, body_url, published,
|
||||
updated, status,
|
||||
updated, approved, status,
|
||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||
team_id, client_side, server_side, license, slug,
|
||||
moderation_message, moderation_message_body
|
||||
@@ -343,6 +360,7 @@ impl Project {
|
||||
follows: m.follows,
|
||||
moderation_message: m.moderation_message,
|
||||
moderation_message_body: m.moderation_message_body,
|
||||
approved: m.approved,
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<Project>>()
|
||||
@@ -499,7 +517,7 @@ impl Project {
|
||||
let id = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM mods
|
||||
WHERE LOWER(slug) = LOWER($1)
|
||||
WHERE slug = LOWER($1)
|
||||
",
|
||||
slug
|
||||
)
|
||||
@@ -523,7 +541,7 @@ impl Project {
|
||||
let id = sqlx::query!(
|
||||
"
|
||||
SELECT id FROM mods
|
||||
WHERE LOWER(slug) = LOWER($1)
|
||||
WHERE slug = LOWER($1)
|
||||
",
|
||||
slug
|
||||
)
|
||||
@@ -607,13 +625,15 @@ impl Project {
|
||||
"
|
||||
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
|
||||
m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,
|
||||
m.updated updated, m.status status,
|
||||
m.updated updated, m.approved approved, m.status status,
|
||||
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,
|
||||
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 || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' '), ' ~~~~ ') gallery,
|
||||
STRING_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url, ' ~~~~ ') donations
|
||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null) categories,
|
||||
ARRAY_AGG(DISTINCT ca.category) filter (where ca.category is not null) additional_categories,
|
||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
||||
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||
ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations
|
||||
FROM mods m
|
||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||
INNER JOIN statuses s ON s.id = m.status
|
||||
@@ -624,6 +644,7 @@ impl Project {
|
||||
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
||||
LEFT JOIN categories c ON mc.joining_category_id = c.id
|
||||
LEFT JOIN categories ca ON mc.joining_category_id = c.id AND mc.is_additional = TRUE
|
||||
LEFT JOIN versions v ON v.mod_id = m.id
|
||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||
WHERE m.id = $1
|
||||
@@ -661,24 +682,44 @@ impl Project {
|
||||
follows: m.follows,
|
||||
moderation_message: m.moderation_message,
|
||||
moderation_message_body: m.moderation_message_body,
|
||||
approved: m.approved,
|
||||
},
|
||||
project_type: m.project_type_name,
|
||||
categories: m
|
||||
.categories
|
||||
.map(|x| x.split(" ~~~~ ").map(|x| x.to_string()).collect())
|
||||
.unwrap_or_default(),
|
||||
versions: m
|
||||
.versions
|
||||
.map(|x| {
|
||||
x.split(" ~~~~ ")
|
||||
.map(|x| VersionId(x.parse().unwrap_or_default()))
|
||||
.collect()
|
||||
})
|
||||
categories: m.categories.unwrap_or_default(),
|
||||
additional_categories: m
|
||||
.additional_categories
|
||||
.unwrap_or_default(),
|
||||
versions: {
|
||||
let versions = m.versions.unwrap_or_default();
|
||||
|
||||
let mut v = versions
|
||||
.into_iter()
|
||||
.flat_map(|x| {
|
||||
let version: Vec<&str> =
|
||||
x.split(" |||| ").collect();
|
||||
|
||||
if version.len() >= 2 {
|
||||
Some((
|
||||
VersionId(
|
||||
version[0].parse().unwrap_or_default(),
|
||||
),
|
||||
convert_postgres_date(version[1])
|
||||
.timestamp(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(VersionId, i64)>>();
|
||||
|
||||
v.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
v.into_iter().map(|x| x.0).collect()
|
||||
},
|
||||
donation_urls: m
|
||||
.donations
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|d| {
|
||||
let strings: Vec<&str> = d.split(" |||| ").collect();
|
||||
|
||||
@@ -700,7 +741,7 @@ impl Project {
|
||||
gallery_items: m
|
||||
.gallery
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|d| {
|
||||
let strings: Vec<&str> = d.split(" |||| ").collect();
|
||||
|
||||
@@ -758,13 +799,15 @@ impl Project {
|
||||
"
|
||||
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
|
||||
m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,
|
||||
m.updated updated, m.status status,
|
||||
m.updated updated, m.approved approved, m.status status,
|
||||
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,
|
||||
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 || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' '), ' ~~~~ ') gallery,
|
||||
STRING_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url, ' ~~~~ ') donations
|
||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null) categories,
|
||||
ARRAY_AGG(DISTINCT ca.category) filter (where ca.category is not null) additional_categories,
|
||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
||||
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||
ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations
|
||||
FROM mods m
|
||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
||||
INNER JOIN statuses s ON s.id = m.status
|
||||
@@ -774,7 +817,8 @@ impl Project {
|
||||
LEFT JOIN mods_donations md ON md.joining_mod_id = m.id
|
||||
LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id
|
||||
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
||||
LEFT JOIN categories c ON mc.joining_category_id = c.id
|
||||
LEFT JOIN categories c ON mc.joining_category_id = c.id AND mc.is_additional = FALSE
|
||||
LEFT JOIN categories ca ON mc.joining_category_id = c.id AND mc.is_additional = TRUE
|
||||
LEFT JOIN versions v ON v.mod_id = m.id
|
||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||
WHERE m.id = ANY($1)
|
||||
@@ -812,14 +856,40 @@ impl Project {
|
||||
follows: m.follows,
|
||||
moderation_message: m.moderation_message,
|
||||
moderation_message_body: m.moderation_message_body,
|
||||
approved: m.approved
|
||||
},
|
||||
project_type: m.project_type_name,
|
||||
categories: m.categories.map(|x| x.split(" ~~~~ ").map(|x| x.to_string()).collect()).unwrap_or_default(),
|
||||
versions: m.versions.map(|x| x.split(" ~~~~ ").map(|x| VersionId(x.parse().unwrap_or_default())).collect()).unwrap_or_default(),
|
||||
categories: m.categories.unwrap_or_default(),
|
||||
additional_categories: m.additional_categories.unwrap_or_default(),
|
||||
versions: {
|
||||
let versions = m.versions.unwrap_or_default();
|
||||
|
||||
let mut v = versions
|
||||
.into_iter()
|
||||
.flat_map(|x| {
|
||||
let version: Vec<&str> =
|
||||
x.split(" |||| ").collect();
|
||||
|
||||
if version.len() >= 2 {
|
||||
Some((
|
||||
VersionId(version[0].parse().unwrap_or_default()),
|
||||
convert_postgres_date(version[1])
|
||||
.timestamp(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(VersionId, i64)>>();
|
||||
|
||||
v.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
v.into_iter().map(|x| x.0).collect()
|
||||
},
|
||||
gallery_items: m
|
||||
.gallery
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|d| {
|
||||
let strings: Vec<&str> = d.split(" |||| ").collect();
|
||||
|
||||
@@ -840,7 +910,7 @@ impl Project {
|
||||
donation_urls: m
|
||||
.donations
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|d| {
|
||||
let strings: Vec<&str> = d.split(" |||| ").collect();
|
||||
|
||||
@@ -873,6 +943,7 @@ pub struct QueryProject {
|
||||
pub inner: Project,
|
||||
pub project_type: String,
|
||||
pub categories: Vec<String>,
|
||||
pub additional_categories: Vec<String>,
|
||||
pub versions: Vec<VersionId>,
|
||||
pub donation_urls: Vec<DonationUrl>,
|
||||
pub gallery_items: Vec<GalleryItem>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::ids::*;
|
||||
use time::OffsetDateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub struct Report {
|
||||
pub id: ReportId,
|
||||
@@ -9,7 +9,7 @@ pub struct Report {
|
||||
pub user_id: Option<UserId>,
|
||||
pub body: String,
|
||||
pub reporter: UserId,
|
||||
pub created: OffsetDateTime,
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct QueryReport {
|
||||
@@ -20,7 +20,7 @@ pub struct QueryReport {
|
||||
pub user_id: Option<UserId>,
|
||||
pub body: String,
|
||||
pub reporter: UserId,
|
||||
pub created: OffsetDateTime,
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Report {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::ids::{ProjectId, UserId};
|
||||
use time::OffsetDateTime;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
pub struct User {
|
||||
pub id: UserId,
|
||||
@@ -9,7 +9,7 @@ pub struct User {
|
||||
pub email: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub bio: Option<String>,
|
||||
pub created: OffsetDateTime,
|
||||
pub created: DateTime<Utc>,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::ids::*;
|
||||
use super::DatabaseError;
|
||||
use crate::database::models::convert_postgres_date;
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::HashMap;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub struct VersionBuilder {
|
||||
pub version_id: VersionId,
|
||||
@@ -144,7 +144,7 @@ impl VersionBuilder {
|
||||
version_number: self.version_number,
|
||||
changelog: self.changelog,
|
||||
changelog_url: None,
|
||||
date_published: OffsetDateTime::now_utc(),
|
||||
date_published: Utc::now(),
|
||||
downloads: 0,
|
||||
featured: self.featured,
|
||||
version_type: self.version_type,
|
||||
@@ -244,7 +244,7 @@ pub struct Version {
|
||||
pub version_number: String,
|
||||
pub changelog: String,
|
||||
pub changelog_url: Option<String>,
|
||||
pub date_published: OffsetDateTime,
|
||||
pub date_published: DateTime<Utc>,
|
||||
pub downloads: i32,
|
||||
pub version_type: String,
|
||||
pub featured: bool,
|
||||
@@ -614,10 +614,10 @@ impl Version {
|
||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||
v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,
|
||||
v.version_type version_type, v.featured featured,
|
||||
STRING_AGG(DISTINCT gv.version || ' |||| ' || gv.created, ' ~~~~ ') game_versions, STRING_AGG(DISTINCT l.loader, ' ~~~~ ') loaders,
|
||||
STRING_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename, ' ~~~~ ') files,
|
||||
STRING_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id, ' ~~~~ ') hashes,
|
||||
STRING_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' '), ' ~~~~ ') dependencies
|
||||
ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
||||
ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,
|
||||
ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,
|
||||
ARRAY_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' ')) filter (where d.dependency_type is not null) dependencies
|
||||
FROM versions v
|
||||
LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id
|
||||
LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id
|
||||
@@ -649,7 +649,7 @@ impl Version {
|
||||
let hashes: Vec<(FileId, String, Vec<u8>)> = v
|
||||
.hashes
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|f| {
|
||||
let hash: Vec<&str> = f.split(" |||| ").collect();
|
||||
|
||||
@@ -667,7 +667,7 @@ impl Version {
|
||||
|
||||
v.files
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|f| {
|
||||
let file: Vec<&str> = f.split(" |||| ").collect();
|
||||
|
||||
@@ -703,38 +703,33 @@ impl Version {
|
||||
let game_versions = v.game_versions.unwrap_or_default();
|
||||
|
||||
let mut gv = game_versions
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|x| {
|
||||
let version: Vec<&str> =
|
||||
x.split(" |||| ").collect();
|
||||
|
||||
if version.len() >= 2 {
|
||||
Some((
|
||||
version[0],
|
||||
version[0].to_string(),
|
||||
convert_postgres_date(version[1])
|
||||
.unix_timestamp(),
|
||||
.timestamp(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(&str, i64)>>();
|
||||
.collect::<Vec<(String, i64)>>();
|
||||
|
||||
gv.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
gv.into_iter().map(|x| x.0.to_string()).collect()
|
||||
gv.into_iter().map(|x| x.0).collect()
|
||||
},
|
||||
loaders: v
|
||||
.loaders
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.map(|x| x.to_string())
|
||||
.collect(),
|
||||
loaders: v.loaders.unwrap_or_default(),
|
||||
featured: v.featured,
|
||||
dependencies: v
|
||||
.dependencies
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
.flat_map(|f| {
|
||||
let dependency: Vec<&str> = f.split(" |||| ").collect();
|
||||
|
||||
@@ -789,10 +784,10 @@ impl Version {
|
||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||
v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,
|
||||
v.version_type version_type, v.featured featured,
|
||||
STRING_AGG(DISTINCT gv.version || ' |||| ' || gv.created, ' ~~~~ ') game_versions, STRING_AGG(DISTINCT l.loader, ' ~~~~ ') loaders,
|
||||
STRING_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename, ' ~~~~ ') files,
|
||||
STRING_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id, ' ~~~~ ') hashes,
|
||||
STRING_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' '), ' ~~~~ ') dependencies
|
||||
ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
|
||||
ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,
|
||||
ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,
|
||||
ARRAY_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' ')) filter (where d.dependency_type is not null) dependencies
|
||||
FROM versions v
|
||||
LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id
|
||||
LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id
|
||||
@@ -821,7 +816,9 @@ impl Version {
|
||||
date_published: v.date_published,
|
||||
downloads: v.downloads,
|
||||
files: {
|
||||
let hashes: Vec<(FileId, String, Vec<u8>)> = v.hashes.unwrap_or_default().split(" ~~~~ ").flat_map(|f| {
|
||||
let hashes: Vec<(FileId, String, Vec<u8>)> = v.hashes.unwrap_or_default()
|
||||
.into_iter()
|
||||
.flat_map(|f| {
|
||||
let hash: Vec<&str> = f.split(" |||| ").collect();
|
||||
|
||||
if hash.len() >= 3 {
|
||||
@@ -835,7 +832,9 @@ impl Version {
|
||||
}
|
||||
}).collect();
|
||||
|
||||
v.files.unwrap_or_default().split(" ~~~~ ").flat_map(|f| {
|
||||
v.files.unwrap_or_default()
|
||||
.into_iter()
|
||||
.flat_map(|f| {
|
||||
let file: Vec<&str> = f.split(" |||| ").collect();
|
||||
|
||||
if file.len() >= 5 {
|
||||
@@ -867,29 +866,31 @@ impl Version {
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut gv = game_versions
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
|
||||
.flat_map(|x| {
|
||||
let version: Vec<&str> = x.split(" |||| ").collect();
|
||||
|
||||
if version.len() >= 2 {
|
||||
Some((version[0], convert_postgres_date(version[1]).unix_timestamp()))
|
||||
Some((version[0].to_string(), convert_postgres_date(version[1]).timestamp()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<(&str, i64)>>();
|
||||
.collect::<Vec<(String, i64)>>();
|
||||
|
||||
gv.sort_by(|a, b| a.1.cmp(&b.1));
|
||||
|
||||
gv.into_iter()
|
||||
.map(|x| x.0.to_string())
|
||||
.map(|x| x.0)
|
||||
.collect()
|
||||
},
|
||||
loaders: v.loaders.unwrap_or_default().split(" ~~~~ ").map(|x| x.to_string()).collect(),
|
||||
loaders: v.loaders.unwrap_or_default(),
|
||||
featured: v.featured,
|
||||
dependencies: v.dependencies
|
||||
.unwrap_or_default()
|
||||
.split(" ~~~~ ")
|
||||
.into_iter()
|
||||
|
||||
.flat_map(|f| {
|
||||
let dependency: Vec<&str> = f.split(" |||| ").collect();
|
||||
|
||||
@@ -952,7 +953,7 @@ pub struct QueryVersion {
|
||||
pub version_number: String,
|
||||
pub changelog: String,
|
||||
pub changelog_url: Option<String>,
|
||||
pub date_published: OffsetDateTime,
|
||||
pub date_published: DateTime<Utc>,
|
||||
pub downloads: i32,
|
||||
|
||||
pub version_type: String,
|
||||
|
||||
Reference in New Issue
Block a user