Queue Dates + Warnings, some cleanup (#549)

* Queue Dates + Warnings, some cleanup

* Fix ping

* Fix repeated discord messaging

* Fix compile error + run fmt
This commit is contained in:
Geometrically
2023-03-14 14:48:46 -07:00
committed by GitHub
parent 150329dd4a
commit 630a71c46c
23 changed files with 701 additions and 2212 deletions

View File

@@ -0,0 +1,2 @@
-- Add migration script here
ALTER TABLE mods ADD COLUMN queued timestamptz NULL

File diff suppressed because it is too large Load Diff

View File

@@ -121,45 +121,15 @@ impl Notification {
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
SELECT n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type,
JSONB_AGG(DISTINCT jsonb_build_object('id', na.id, 'notification_id', na.notification_id, 'title', na.title, 'action_route_method', na.action_route_method, 'action_route', na.action_route)) 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
GROUP BY n.id, n.user_id;
",
id as NotificationId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(Notification {
id,
user_id: UserId(row.user_id),
notification_type: row.notification_type,
title: row.title,
text: row.text,
link: row.link,
read: row.read,
created: row.created,
actions: serde_json::from_value(
row.actions.unwrap_or_default(),
)
.ok()
.unwrap_or_default(),
}))
} else {
Ok(None)
}
Self::get_many(&[id], executor)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_many<'a, E>(
notification_ids: Vec<NotificationId>,
notification_ids: &[NotificationId],
exec: E,
) -> Result<Vec<Notification>, sqlx::Error>
where
@@ -168,7 +138,7 @@ impl Notification {
use futures::stream::TryStreamExt;
let notification_ids_parsed: Vec<i64> =
notification_ids.into_iter().map(|x| x.0).collect();
notification_ids.iter().map(|x| x.0).collect();
sqlx::query!(
"
SELECT n.id, n.user_id, n.title, n.text, n.link, n.created, n.read, n.type notification_type,
@@ -257,35 +227,15 @@ impl Notification {
id: NotificationId,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Option<()>, sqlx::error::Error> {
sqlx::query!(
"
DELETE FROM notifications_actions
WHERE notification_id = $1
",
id as NotificationId,
)
.execute(&mut *transaction)
.await?;
sqlx::query!(
"
DELETE FROM notifications
WHERE id = $1
",
id as NotificationId,
)
.execute(&mut *transaction)
.await?;
Ok(Some(()))
Self::remove_many(&[id], transaction).await
}
pub async fn remove_many(
notification_ids: Vec<NotificationId>,
notification_ids: &[NotificationId],
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Option<()>, sqlx::error::Error> {
let notification_ids_parsed: Vec<i64> =
notification_ids.into_iter().map(|x| x.0).collect();
notification_ids.iter().map(|x| x.0).collect();
sqlx::query!(
"

View File

@@ -119,6 +119,11 @@ impl ProjectBuilder {
published: Utc::now(),
updated: Utc::now(),
approved: None,
queued: if self.status == ProjectStatus::Processing {
Some(Utc::now())
} else {
None
},
status: self.status,
requested_status: self.requested_status,
downloads: 0,
@@ -202,6 +207,7 @@ pub struct Project {
pub published: DateTime<Utc>,
pub updated: DateTime<Utc>,
pub approved: Option<DateTime<Utc>>,
pub queued: Option<DateTime<Utc>>,
pub status: ProjectStatus,
pub requested_status: Option<ProjectStatus>,
pub downloads: i32,
@@ -281,69 +287,15 @@ impl Project {
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
SELECT project_type, title, description, downloads, follows,
icon_url, body, published,
updated, approved, status, requested_status,
issues_url, source_url, wiki_url, discord_url, license_url,
team_id, client_side, server_side, license, slug,
moderation_message, moderation_message_body, flame_anvil_project,
flame_anvil_user, webhook_sent, color, loaders, game_versions
FROM mods
WHERE id = $1
",
id as ProjectId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(Project {
id,
project_type: ProjectTypeId(row.project_type),
team_id: TeamId(row.team_id),
title: row.title,
description: row.description,
downloads: row.downloads,
body_url: None,
icon_url: row.icon_url,
published: row.published,
updated: row.updated,
issues_url: row.issues_url,
source_url: row.source_url,
wiki_url: row.wiki_url,
license_url: row.license_url,
discord_url: row.discord_url,
client_side: SideTypeId(row.client_side),
status: ProjectStatus::from_str(&row.status),
requested_status: row
.requested_status
.map(|x| ProjectStatus::from_str(&x)),
server_side: SideTypeId(row.server_side),
license: row.license,
slug: row.slug,
body: row.body,
follows: row.follows,
moderation_message: row.moderation_message,
moderation_message_body: row.moderation_message_body,
approved: row.approved,
flame_anvil_project: row.flame_anvil_project,
flame_anvil_user: row.flame_anvil_user.map(UserId),
webhook_sent: row.webhook_sent,
color: row.color.map(|x| x as u32),
loaders: row.loaders,
game_versions: row.game_versions,
}))
} else {
Ok(None)
}
Project::get_many(&[id], executor)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_many<'a, E>(
project_ids: Vec<ProjectId>,
project_ids: &[ProjectId],
exec: E,
) -> Result<Vec<Project>, sqlx::Error>
where
@@ -352,12 +304,12 @@ impl Project {
use futures::stream::TryStreamExt;
let project_ids_parsed: Vec<i64> =
project_ids.into_iter().map(|x| x.0).collect();
project_ids.iter().map(|x| x.0).collect();
let projects = sqlx::query!(
"
SELECT id, project_type, title, description, downloads, follows,
icon_url, body, published,
updated, approved, status, requested_status,
updated, approved, queued, status, requested_status,
issues_url, source_url, wiki_url, discord_url, license_url,
team_id, client_side, server_side, license, slug,
moderation_message, moderation_message_body, flame_anvil_project,
@@ -406,6 +358,7 @@ impl Project {
color: m.color.map(|x| x as u32),
loaders: m.loaders,
game_versions: m.game_versions,
queued: m.queued,
}))
})
.try_collect::<Vec<Project>>()
@@ -677,125 +630,9 @@ impl Project {
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
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.published published,
m.updated updated, m.approved approved, m.status status, m.requested_status requested_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,
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent webhook_sent, m.color,
m.loaders loaders, m.game_versions game_versions,
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,
JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions,
JSONB_AGG(DISTINCT jsonb_build_object('image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering)) filter (where mg.image_url is not null) gallery,
JSONB_AGG(DISTINCT jsonb_build_object('platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', 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 side_types cs ON m.client_side = cs.id
INNER JOIN side_types ss ON m.server_side = ss.id
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 versions v ON v.mod_id = m.id AND v.status = ANY($2)
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
WHERE m.id = $1
GROUP BY pt.id, cs.id, ss.id, m.id;
",
id as ProjectId,
&*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_listed()).map(|x| x.to_string()).collect::<Vec<String>>()
)
.fetch_optional(executor)
.await?;
if let Some(m) = result {
Ok(Some(QueryProject {
inner: Project {
id: ProjectId(m.id),
project_type: ProjectTypeId(m.project_type),
team_id: TeamId(m.team_id),
title: m.title.clone(),
description: m.description.clone(),
downloads: m.downloads,
body_url: None,
icon_url: m.icon_url.clone(),
published: m.published,
updated: m.updated,
issues_url: m.issues_url.clone(),
source_url: m.source_url.clone(),
wiki_url: m.wiki_url.clone(),
license_url: m.license_url.clone(),
discord_url: m.discord_url.clone(),
client_side: SideTypeId(m.client_side),
status: ProjectStatus::from_str(&m.status),
requested_status: m
.requested_status
.map(|x| ProjectStatus::from_str(&x)),
server_side: SideTypeId(m.server_side),
license: m.license.clone(),
slug: m.slug.clone(),
body: m.body.clone(),
follows: m.follows,
moderation_message: m.moderation_message,
moderation_message_body: m.moderation_message_body,
approved: m.approved,
flame_anvil_project: m.flame_anvil_project,
flame_anvil_user: m.flame_anvil_user.map(UserId),
webhook_sent: m.webhook_sent,
color: m.color.map(|x| x as u32),
loaders: m.loaders,
game_versions: m.game_versions,
},
project_type: m.project_type_name,
categories: m.categories.unwrap_or_default(),
additional_categories: m
.additional_categories
.unwrap_or_default(),
versions: {
#[derive(Deserialize)]
struct Version {
pub id: VersionId,
pub date_published: DateTime<Utc>,
}
let mut versions: Vec<Version> =
serde_json::from_value(m.versions.unwrap_or_default())
.ok()
.unwrap_or_default();
versions.sort_by(|a, b| {
a.date_published.cmp(&b.date_published)
});
versions.into_iter().map(|x| x.id).collect()
},
gallery_items: {
let mut gallery: Vec<GalleryItem> =
serde_json::from_value(m.gallery.unwrap_or_default())
.ok()
.unwrap_or_default();
gallery.sort_by(|a, b| a.ordering.cmp(&b.ordering));
gallery
},
donation_urls: serde_json::from_value(
m.donations.unwrap_or_default(),
)
.ok()
.unwrap_or_default(),
client_side: crate::models::projects::SideType::from_str(
&m.client_side_type,
),
server_side: crate::models::projects::SideType::from_str(
&m.server_side_type,
),
}))
} else {
Ok(None)
}
Project::get_many_full(&[id], executor)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_many_full<'a, E>(
@@ -813,7 +650,7 @@ 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.published published,
m.updated updated, m.approved approved, m.status status, m.requested_status requested_status,
m.updated updated, m.approved approved, m.queued, m.status status, m.requested_status requested_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,
cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent, m.color,
@@ -882,6 +719,7 @@ impl Project {
color: m.color.map(|x| x as u32),
loaders: m.loaders,
game_versions: m.game_versions,
queued: m.queued,
},
project_type: m.project_type_name,
categories: m.categories.unwrap_or_default(),

View File

@@ -60,36 +60,13 @@ impl Report {
where
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
SELECT rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created
FROM reports r
INNER JOIN report_types rt ON rt.id = r.report_type_id
WHERE r.id = $1
",
id as ReportId,
)
.fetch_optional(exec)
.await?;
if let Some(row) = result {
Ok(Some(QueryReport {
id,
report_type: row.name,
project_id: row.mod_id.map(ProjectId),
version_id: row.version_id.map(VersionId),
user_id: row.user_id.map(UserId),
body: row.body,
reporter: UserId(row.reporter),
created: row.created,
}))
} else {
Ok(None)
}
Self::get_many(&[id], exec)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_many<'a, E>(
report_ids: Vec<ReportId>,
report_ids: &[ReportId],
exec: E,
) -> Result<Vec<QueryReport>, sqlx::Error>
where
@@ -98,7 +75,7 @@ impl Report {
use futures::stream::TryStreamExt;
let report_ids_parsed: Vec<i64> =
report_ids.into_iter().map(|x| x.0).collect();
report_ids.iter().map(|x| x.0).collect();
let reports = sqlx::query!(
"
SELECT r.id, rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created

View File

@@ -157,71 +157,13 @@ impl TeamMember {
executor: E,
) -> Result<Vec<QueryTeamMember>, super::DatabaseError>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
use futures::stream::TryStreamExt;
let team_members = sqlx::query!(
"
SELECT tm.id id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering ordering,
u.id user_id, u.github_id github_id, u.name user_name, u.email email,
u.avatar_url avatar_url, u.username username, u.bio bio,
u.created created, u.role user_role, u.badges badges, u.balance balance,
u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,
u.payout_address payout_address, u.flame_anvil_key flame_anvil_key
FROM team_members tm
INNER JOIN users u ON u.id = tm.user_id
WHERE tm.team_id = $1
ORDER BY tm.ordering
",
id as TeamId,
)
.fetch_many(executor)
.try_filter_map(|e| async {
if let Some(m) = e.right() {
Ok(Some(Ok(QueryTeamMember {
id: TeamMemberId(m.id),
team_id: id,
role: m.member_role,
permissions: Permissions::from_bits(m.permissions as u64).unwrap_or_default(),
accepted: m.accepted,
user: User {
id: UserId(m.user_id),
github_id: m.github_id,
name: m.user_name,
email: m.email,
avatar_url: m.avatar_url,
username: m.username,
bio: m.bio,
created: m.created,
role: m.user_role,
badges: Badges::from_bits(m.badges as u64).unwrap_or_default(),
balance: m.balance,
payout_wallet: m.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
payout_wallet_type: m.payout_wallet_type.map(|x| RecipientType::from_string(&x)),
payout_address: m.payout_address,
flame_anvil_key: m.flame_anvil_key,
},
payouts_split: m.payouts_split,
ordering: m.ordering,
})))
} else {
Ok(None)
}
})
.try_collect::<Vec<Result<QueryTeamMember, super::DatabaseError>>>()
.await?;
let team_members = team_members
.into_iter()
.collect::<Result<Vec<QueryTeamMember>, super::DatabaseError>>()?;
Ok(team_members)
Self::get_from_team_full_many(&[id], executor).await
}
pub async fn get_from_team_full_many<'a, E>(
team_ids: Vec<TeamId>,
team_ids: &[TeamId],
exec: E,
) -> Result<Vec<QueryTeamMember>, super::DatabaseError>
where
@@ -229,8 +171,7 @@ impl TeamMember {
{
use futures::stream::TryStreamExt;
let team_ids_parsed: Vec<i64> =
team_ids.into_iter().map(|x| x.0).collect();
let team_ids_parsed: Vec<i64> = team_ids.iter().map(|x| x.0).collect();
let teams = sqlx::query!(
"
@@ -394,38 +335,14 @@ impl TeamMember {
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT id, user_id, role, permissions, accepted, payouts_split, ordering
FROM team_members
WHERE (team_id = $1 AND user_id = $2 AND accepted = TRUE)
",
id as TeamId,
user_id as UserId
)
.fetch_optional(executor)
.await?;
if let Some(m) = result {
Ok(Some(TeamMember {
id: TeamMemberId(m.id),
team_id: id,
user_id,
role: m.role,
permissions: Permissions::from_bits(m.permissions as u64)
.unwrap_or_default(),
accepted: m.accepted,
payouts_split: m.payouts_split,
ordering: m.ordering,
}))
} else {
Ok(None)
}
Self::get_from_user_id_many(&[id], user_id, executor)
.await
.map(|x| x.into_iter().next())
}
/// Gets team members from user ids and team ids. Does not return pending members.
pub async fn get_from_user_id_many<'a, 'b, E>(
team_ids: Vec<TeamId>,
team_ids: &[TeamId],
user_id: UserId,
executor: E,
) -> Result<Vec<Self>, super::DatabaseError>
@@ -434,8 +351,7 @@ impl TeamMember {
{
use futures::stream::TryStreamExt;
let team_ids_parsed: Vec<i64> =
team_ids.into_iter().map(|x| x.0).collect();
let team_ids_parsed: Vec<i64> = team_ids.iter().map(|x| x.0).collect();
let team_members = sqlx::query!(
"

View File

@@ -56,49 +56,11 @@ impl User {
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
SELECT u.github_id, u.name, u.email,
u.avatar_url, u.username, u.bio,
u.created, u.role, u.badges,
u.balance, u.payout_wallet, u.payout_wallet_type,
u.payout_address, u.flame_anvil_key
FROM users u
WHERE u.id = $1
",
id as UserId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(User {
id,
github_id: row.github_id,
name: row.name,
email: row.email,
avatar_url: row.avatar_url,
username: row.username,
bio: row.bio,
created: row.created,
role: row.role,
badges: Badges::from_bits(row.badges as u64)
.unwrap_or_default(),
balance: row.balance,
payout_wallet: row
.payout_wallet
.map(|x| RecipientWallet::from_string(&x)),
payout_wallet_type: row
.payout_wallet_type
.map(|x| RecipientType::from_string(&x)),
payout_address: row.payout_address,
flame_anvil_key: row.flame_anvil_key,
}))
} else {
Ok(None)
}
Self::get_many(&[id], executor)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_from_github_id<'a, 'b, E>(
@@ -202,7 +164,7 @@ impl User {
}
pub async fn get_many<'a, E>(
user_ids: Vec<UserId>,
user_ids: &[UserId],
exec: E,
) -> Result<Vec<User>, sqlx::Error>
where
@@ -210,8 +172,7 @@ impl User {
{
use futures::stream::TryStreamExt;
let user_ids_parsed: Vec<i64> =
user_ids.into_iter().map(|x| x.0).collect();
let user_ids_parsed: Vec<i64> = user_ids.iter().map(|x| x.0).collect();
let users = sqlx::query!(
"
SELECT u.id, u.github_id, u.name, u.email,

View File

@@ -573,46 +573,15 @@ impl Version {
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
SELECT v.mod_id, v.author_id, v.name, v.version_number,
v.changelog, v.date_published, v.downloads,
v.version_type, v.featured, v.status, v.requested_status
FROM versions v
WHERE v.id = $1
",
id as VersionId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(Version {
id,
project_id: ProjectId(row.mod_id),
author_id: UserId(row.author_id),
name: row.name,
version_number: row.version_number,
changelog: row.changelog,
changelog_url: None,
date_published: row.date_published,
downloads: row.downloads,
version_type: row.version_type,
featured: row.featured,
status: VersionStatus::from_str(&row.status),
requested_status: row
.requested_status
.map(|x| VersionStatus::from_str(&x)),
}))
} else {
Ok(None)
}
Self::get_many(&[id], executor)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_many<'a, E>(
version_ids: Vec<VersionId>,
version_ids: &[VersionId],
exec: E,
) -> Result<Vec<Version>, sqlx::Error>
where
@@ -621,7 +590,7 @@ impl Version {
use futures::stream::TryStreamExt;
let version_ids_parsed: Vec<i64> =
version_ids.into_iter().map(|x| x.0).collect();
version_ids.iter().map(|x| x.0).collect();
let versions = sqlx::query!(
"
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
@@ -664,151 +633,15 @@ impl Version {
executor: E,
) -> Result<Option<QueryVersion>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
{
let result = sqlx::query!(
"
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.date_published date_published, v.downloads downloads,
v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status,
JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,
ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,
JSONB_AGG(DISTINCT jsonb_build_object('id', f.id, 'url', f.url, 'filename', f.filename, 'primary', f.is_primary, 'size', f.size, 'file_type', f.file_type)) filter (where f.id is not null) files,
JSONB_AGG(DISTINCT jsonb_build_object('algorithm', h.algorithm, 'hash', encode(h.hash, 'escape'), 'file_id', h.file_id)) filter (where h.hash is not null) hashes,
JSONB_AGG(DISTINCT jsonb_build_object('project_id', d.mod_dependency_id, 'version_id', d.dependency_id, 'dependency_type', d.dependency_type,'file_name', 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
LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id
LEFT OUTER JOIN loaders l on lv.loader_id = l.id
LEFT OUTER JOIN files f on v.id = f.version_id
LEFT OUTER JOIN hashes h on f.id = h.file_id
LEFT OUTER JOIN dependencies d on v.id = d.dependent_id
WHERE v.id = $1
GROUP BY v.id;
",
id as VersionId,
)
.fetch_optional(executor)
.await?;
if let Some(v) = result {
Ok(Some(QueryVersion {
inner: Version {
id: VersionId(v.id),
project_id: ProjectId(v.mod_id),
author_id: UserId(v.author_id),
name: v.version_name,
version_number: v.version_number,
changelog: v.changelog,
changelog_url: None,
date_published: v.date_published,
downloads: v.downloads,
version_type: v.version_type,
featured: v.featured,
status: VersionStatus::from_str(&v.status),
requested_status: v
.requested_status
.map(|x| VersionStatus::from_str(&x)),
},
files: {
#[derive(Deserialize, Debug)]
struct Hash {
pub file_id: FileId,
pub algorithm: String,
pub hash: String,
}
#[derive(Deserialize, Debug)]
struct File {
pub id: FileId,
pub url: String,
pub filename: String,
pub primary: bool,
pub size: u32,
pub file_type: Option<FileType>,
}
let hashes: Vec<Hash> =
serde_json::from_value(v.hashes.unwrap_or_default())
.ok()
.unwrap_or_default();
let files: Vec<File> =
serde_json::from_value(v.files.unwrap_or_default())
.ok()
.unwrap_or_default();
let mut files = files
.into_iter()
.map(|x| {
let mut file_hashes = HashMap::new();
for hash in &hashes {
if hash.file_id == x.id {
file_hashes.insert(
hash.algorithm.clone(),
hash.hash.clone(),
);
}
}
QueryFile {
id: x.id,
url: x.url,
filename: x.filename,
hashes: file_hashes,
primary: x.primary,
size: x.size,
file_type: x.file_type,
}
})
.collect::<Vec<_>>();
files.sort_by(|a, b| {
if a.primary {
Ordering::Less
} else if b.primary {
Ordering::Greater
} else {
a.filename.cmp(&b.filename)
}
});
files
},
game_versions: {
#[derive(Deserialize)]
struct GameVersion {
pub version: String,
pub created: DateTime<Utc>,
}
let mut game_versions: Vec<GameVersion> =
serde_json::from_value(
v.game_versions.unwrap_or_default(),
)
.ok()
.unwrap_or_default();
game_versions.sort_by(|a, b| a.created.cmp(&b.created));
game_versions.into_iter().map(|x| x.version).collect()
},
loaders: v.loaders.unwrap_or_default(),
dependencies: serde_json::from_value(
v.dependencies.unwrap_or_default(),
)
.ok()
.unwrap_or_default(),
}))
} else {
Ok(None)
}
Self::get_many_full(&[id], executor)
.await
.map(|x| x.into_iter().next())
}
pub async fn get_many_full<'a, E>(
version_ids: Vec<VersionId>,
version_ids: &[VersionId],
exec: E,
) -> Result<Vec<QueryVersion>, sqlx::Error>
where
@@ -817,7 +650,7 @@ impl Version {
use futures::stream::TryStreamExt;
let version_ids_parsed: Vec<i64> =
version_ids.into_iter().map(|x| x.0).collect();
version_ids.iter().map(|x| x.0).collect();
sqlx::query!(
"
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,

View File

@@ -7,6 +7,7 @@ use crate::ratelimit::middleware::RateLimiter;
use crate::util::env::{parse_strings_from_var, parse_var};
use actix_cors::Cors;
use actix_web::{web, App, HttpServer};
use chrono::{DateTime, Utc};
use env_logger::Env;
use log::{error, info, warn};
use search::indexing::index_projects;
@@ -201,6 +202,74 @@ async fn main() -> std::io::Result<()> {
}
});
// Reminding moderators to review projects which have been in the queue longer than 24hr
let pool_ref = pool.clone();
let webhook_message_sent = Arc::new(Mutex::new(Vec::<(
database::models::ProjectId,
DateTime<Utc>,
)>::new()));
scheduler.run(std::time::Duration::from_secs(10 * 60), move || {
let pool_ref = pool_ref.clone();
let webhook_message_sent_ref = webhook_message_sent.clone();
info!("Checking reviewed projects submitted more than 24hrs ago");
async move {
let do_steps = async {
use futures::TryStreamExt;
let project_ids = sqlx::query!(
"
SELECT id FROM mods
WHERE status = $1 AND queued < NOW() - INTERVAL '1 day'
ORDER BY updated ASC
",
crate::models::projects::ProjectStatus::Processing.as_str(),
)
.fetch_many(&pool_ref)
.try_filter_map(|e| async {
Ok(e.right().map(|m| database::models::ProjectId(m.id)))
})
.try_collect::<Vec<database::models::ProjectId>>()
.await?;
let mut webhook_message_sent_ref = webhook_message_sent_ref.lock().await;
webhook_message_sent_ref.retain(|x| Utc::now() - x.1 > chrono::Duration::hours(12));
for project in project_ids {
if webhook_message_sent_ref.iter().any(|x| x.0 == project) { continue; }
if let Ok(webhook_url) =
dotenvy::var("MODERATION_DISCORD_WEBHOOK")
{
util::webhook::send_discord_webhook(
project.into(),
&pool_ref,
webhook_url,
Some("<@&783155186491195394> This project has been in the queue for over 24 hours!".to_string()),
)
.await
.ok();
webhook_message_sent_ref.push((project, Utc::now()));
}
}
Ok::<(), crate::routes::ApiError>(())
};
if let Err(e) = do_steps.await {
warn!(
"Checking reviewed projects submitted more than 24hrs ago failed: {:?}",
e
);
}
info!("Finished checking reviewed projects submitted more than 24hrs ago");
}
});
scheduler::schedule_versions(&mut scheduler, pool.clone());
let download_queue = Arc::new(DownloadQueue::new());

View File

@@ -48,6 +48,8 @@ pub struct Project {
/// The date at which the project was first approved.
//pub approved: Option<DateTime<Utc>>,
pub approved: Option<DateTime<Utc>>,
/// The date at which the project entered the moderation queue
pub queued: Option<DateTime<Utc>>,
/// The status of the project
pub status: ProjectStatus,
@@ -122,6 +124,7 @@ impl From<QueryProject> for Project {
published: m.published,
updated: m.updated,
approved: m.approved,
queued: m.queued,
status: m.status,
requested_status: m.requested_status,
moderator_message: if let Some(message) = m.moderation_message {

View File

@@ -30,7 +30,7 @@ pub async fn get_projects(
"
SELECT id FROM mods
WHERE status = $1
ORDER BY updated ASC
ORDER BY queued ASC
LIMIT $2;
",
ProjectStatus::Processing.as_str(),

View File

@@ -32,7 +32,7 @@ pub async fn notifications_get(
let notifications_data: Vec<DBNotification> =
database::models::notification_item::Notification::get_many(
notification_ids,
&notification_ids,
&**pool,
)
.await?;
@@ -127,13 +127,13 @@ pub async fn notifications_delete(
serde_json::from_str::<Vec<NotificationId>>(&ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
.collect::<Vec<_>>();
let mut transaction = pool.begin().await?;
let notifications_data =
database::models::notification_item::Notification::get_many(
notification_ids,
&notification_ids,
&**pool,
)
.await?;
@@ -148,7 +148,7 @@ pub async fn notifications_delete(
}
database::models::notification_item::Notification::remove_many(
notifications,
&notifications,
&mut transaction,
)
.await?;

View File

@@ -802,6 +802,7 @@ async fn project_create_inner(
published: now,
updated: now,
approved: None,
queued: None,
status,
requested_status: project_builder.requested_status,
moderator_message: None,
@@ -844,6 +845,7 @@ async fn project_create_inner(
response.id,
pool,
webhook_url,
None,
)
.await
.ok();

View File

@@ -256,12 +256,13 @@ pub async fn dependency_list(
})
.collect::<Vec<_>>();
let dep_version_ids = dependencies
.iter()
.filter_map(|x| x.0)
.collect::<Vec<database::models::VersionId>>();
let (projects_result, versions_result) = futures::future::try_join(
database::Project::get_many_full(&project_ids, &**pool),
database::Version::get_many_full(
dependencies.iter().filter_map(|x| x.0).collect(),
&**pool,
),
database::Version::get_many_full(&dep_version_ids, &**pool),
)
.await?;
@@ -502,18 +503,7 @@ pub async fn project_edit(
sqlx::query!(
"
UPDATE mods
SET moderation_message = NULL
WHERE (id = $1)
",
id as database::models::ids::ProjectId,
)
.execute(&mut *transaction)
.await?;
sqlx::query!(
"
UPDATE mods
SET moderation_message_body = NULL
SET moderation_message = NULL, moderation_message_body = NULL, queued = NOW()
WHERE (id = $1)
",
id as database::models::ids::ProjectId,
@@ -528,6 +518,7 @@ pub async fn project_edit(
project_item.inner.id.into(),
&pool,
webhook_url,
None,
)
.await
.ok();
@@ -557,6 +548,7 @@ pub async fn project_edit(
project_item.inner.id.into(),
&pool,
webhook_url,
None,
)
.await
.ok();
@@ -1234,9 +1226,12 @@ pub async fn projects_edit(
)));
}
let team_ids = projects_data
.iter()
.map(|x| x.inner.team_id)
.collect::<Vec<database::models::TeamId>>();
let team_members = database::models::TeamMember::get_from_team_full_many(
projects_data.iter().map(|x| x.inner.team_id).collect(),
&**pool,
&team_ids, &**pool,
)
.await?;

View File

@@ -184,7 +184,8 @@ pub async fn reports(
.await?;
let query_reports = crate::database::models::report_item::Report::get_many(
report_ids, &**pool,
&report_ids,
&**pool,
)
.await?;

View File

@@ -121,11 +121,11 @@ pub async fn teams_get(
.collect::<Vec<crate::database::models::ids::TeamId>>();
let teams_data =
TeamMember::get_from_team_full_many(team_ids.clone(), &**pool).await?;
TeamMember::get_from_team_full_many(&team_ids, &**pool).await?;
let current_user = get_user_from_headers(req.headers(), &**pool).await.ok();
let accepted = if let Some(user) = current_user {
TeamMember::get_from_user_id_many(team_ids, user.id.into(), &**pool)
TeamMember::get_from_user_id_many(&team_ids, user.id.into(), &**pool)
.await?
.into_iter()
.map(|m| m.team_id.0)

View File

@@ -45,7 +45,7 @@ pub async fn forge_updates(
.await?;
let versions =
database::models::Version::get_many_full(version_ids, &**pool).await?;
database::models::Version::get_many_full(&version_ids, &**pool).await?;
let mut versions =
filter_authorized_versions(versions, &user_option, &pool).await?;

View File

@@ -44,9 +44,9 @@ pub async fn users_get(
let user_ids = serde_json::from_str::<Vec<UserId>>(&ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
.collect::<Vec<crate::database::models::UserId>>();
let users_data = User::get_many(user_ids, &**pool).await?;
let users_data = User::get_many(&user_ids, &**pool).await?;
let users: Vec<crate::models::users::User> =
users_data.into_iter().map(From::from).collect();

View File

@@ -31,7 +31,7 @@ pub async fn mods_list(
let project_data = User::get_projects(id, &**pool).await?;
let response: Vec<_> =
crate::database::Project::get_many(project_data, &**pool)
crate::database::Project::get_many(&project_data, &**pool)
.await?
.into_iter()
.filter(|x| can_view_private || x.status.is_approved())

View File

@@ -84,7 +84,7 @@ pub async fn version_list(
.await?;
let mut versions =
database::models::Version::get_many_full(version_ids, &**pool)
database::models::Version::get_many_full(&version_ids, &**pool)
.await?;
let mut response = versions
@@ -165,9 +165,9 @@ pub async fn versions_get(
let version_ids = serde_json::from_str::<Vec<VersionId>>(&ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
.collect::<Vec<database::models::VersionId>>();
let versions_data =
database::models::Version::get_many_full(version_ids, &**pool).await?;
database::models::Version::get_many_full(&version_ids, &**pool).await?;
let mut versions = Vec::new();

View File

@@ -63,14 +63,12 @@ pub async fn get_version_from_hash(
.fetch_all(&**pool)
.await?;
let versions_data = database::models::Version::get_many_full(
result
.iter()
.map(|x| database::models::VersionId(x.version_id))
.collect(),
&**pool,
)
.await?;
let version_ids = result
.iter()
.map(|x| database::models::VersionId(x.version_id))
.collect::<Vec<_>>();
let versions_data =
database::models::Version::get_many_full(&version_ids, &**pool).await?;
if let Some(first) = versions_data.first() {
if hash_query.multiple {
@@ -357,14 +355,12 @@ pub async fn get_versions_from_hashes(
.fetch_all(&**pool)
.await?;
let versions_data = database::models::Version::get_many_full(
result
.iter()
.map(|x| database::models::VersionId(x.version_id))
.collect(),
&**pool,
)
.await?;
let version_ids = result
.iter()
.map(|x| database::models::VersionId(x.version_id))
.collect::<Vec<_>>();
let versions_data =
database::models::Version::get_many_full(&version_ids, &**pool).await?;
let response: Result<HashMap<String, Version>, ApiError> = result
.into_iter()
@@ -518,11 +514,10 @@ pub async fn update_files(
}
}
let versions = database::models::Version::get_many_full(
version_ids.keys().copied().collect(),
&**pool,
)
.await?;
let query_version_ids = version_ids.keys().copied().collect::<Vec<_>>();
let versions =
database::models::Version::get_many_full(&query_version_ids, &**pool)
.await?;
let mut response = HashMap::new();

View File

@@ -67,7 +67,7 @@ pub async fn version_list(
.await?;
let mut versions =
database::models::Version::get_many_full(version_ids, &**pool)
database::models::Version::get_many_full(&version_ids, &**pool)
.await?;
let mut response = versions
@@ -179,9 +179,9 @@ pub async fn versions_get(
serde_json::from_str::<Vec<models::ids::VersionId>>(&ids.ids)?
.into_iter()
.map(|x| x.into())
.collect();
.collect::<Vec<database::models::VersionId>>();
let versions_data =
database::models::Version::get_many_full(version_ids, &**pool).await?;
database::models::Version::get_many_full(&version_ids, &**pool).await?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();

View File

@@ -55,6 +55,7 @@ struct DiscordWebhook {
pub avatar_url: Option<String>,
pub username: Option<String>,
pub embeds: Vec<DiscordEmbed>,
pub content: Option<String>,
}
const PLUGIN_LOADERS: &[&str] = &[
@@ -72,6 +73,7 @@ pub async fn send_discord_webhook(
project_id: ProjectId,
pool: &PgPool,
webhook_url: String,
message: Option<String>,
) -> Result<(), ApiError> {
let all_game_versions = GameVersion::list(pool).await?;
@@ -257,6 +259,7 @@ pub async fn send_discord_webhook(
),
username: Some("Modrinth Release".to_string()),
embeds: vec![embed],
content: message,
})
.send()
.await