Merge pull request #138 from modrinth/fixes

A number of fixes
This commit is contained in:
Geometrically 2021-01-18 10:46:17 -07:00 committed by GitHub
commit fc2786f5e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 614 additions and 745 deletions

View File

@ -0,0 +1,3 @@
-- Add migration script here
ALTER TABLE versions
DROP COLUMN accepted;

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,8 @@ use super::ids::*;
pub struct DonationUrl {
pub mod_id: ModId,
pub platform_id: DonationPlatformId,
pub platform_short: String,
pub platform_name: String,
pub url: String,
}
@ -429,7 +431,8 @@ impl Mod {
let donations: Vec<DonationUrl> = sqlx::query!(
"
SELECT joining_platform_id, url FROM mods_donations
SELECT d.joining_platform_id, d.url, dp.short, dp.name FROM mods_donations d
INNER JOIN donation_platforms dp ON d.joining_platform_id=dp.id
WHERE joining_mod_id = $1
",
id as ModId,
@ -439,6 +442,8 @@ impl Mod {
Ok(e.right().map(|c| DonationUrl {
mod_id: id,
platform_id: DonationPlatformId(c.joining_platform_id),
platform_short: c.short,
platform_name: c.name,
url: c.url,
}))
})

View File

@ -83,7 +83,6 @@ impl VersionBuilder {
date_published: chrono::Utc::now(),
downloads: 0,
release_channel: self.release_channel,
accepted: false,
featured: self.featured,
};
@ -158,7 +157,6 @@ pub struct Version {
pub date_published: chrono::DateTime<chrono::Utc>,
pub downloads: i32,
pub release_channel: ChannelId,
pub accepted: bool,
pub featured: bool,
}
@ -172,13 +170,13 @@ impl Version {
INSERT INTO versions (
id, mod_id, author_id, name, version_number,
changelog_url, date_published,
downloads, release_channel, accepted, featured
downloads, release_channel, featured
)
VALUES (
$1, $2, $3, $4, $5,
$6, $7,
$8, $9,
$10, $11
$10
)
",
self.id as VersionId,
@ -190,7 +188,6 @@ impl Version {
self.date_published,
self.downloads,
self.release_channel as ChannelId,
self.accepted,
self.featured
)
.execute(&mut *transaction)
@ -385,7 +382,7 @@ impl Version {
"
SELECT v.mod_id, v.author_id, v.name, v.version_number,
v.changelog, v.changelog_url, v.date_published, v.downloads,
v.release_channel, v.accepted, v.featured
v.release_channel, v.featured
FROM versions v
WHERE v.id = $1
",
@ -406,7 +403,6 @@ impl Version {
date_published: row.date_published,
downloads: row.downloads,
release_channel: ChannelId(row.release_channel),
accepted: row.accepted,
featured: row.featured,
}))
} else {
@ -428,7 +424,7 @@ impl Version {
"
SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,
v.changelog, v.changelog_url, v.date_published, v.downloads,
v.release_channel, v.accepted, v.featured
v.release_channel, v.featured
FROM versions v
WHERE v.id IN (SELECT * FROM UNNEST($1::bigint[]))
",
@ -447,7 +443,6 @@ impl Version {
date_published: v.date_published,
downloads: v.downloads,
release_channel: ChannelId(v.release_channel),
accepted: v.accepted,
featured: v.featured,
}))
})
@ -468,7 +463,7 @@ impl Version {
"
SELECT v.mod_id, v.author_id, v.name, v.version_number,
v.changelog, v.changelog_url, v.date_published, v.downloads,
release_channels.channel, v.accepted, v.featured
release_channels.channel, v.featured
FROM versions v
INNER JOIN release_channels ON v.release_channel = release_channels.id
WHERE v.id = $1
@ -486,6 +481,7 @@ impl Version {
SELECT gv.version FROM game_versions_versions gvv
INNER JOIN game_versions gv ON gvv.game_version_id=gv.id
WHERE gvv.joining_version_id = $1
ORDER BY gv.created
",
id as VersionId,
)
@ -558,7 +554,6 @@ impl Version {
files,
loaders,
game_versions,
accepted: row.accepted,
featured: row.featured,
}))
} else {
@ -613,7 +608,6 @@ pub struct QueryVersion {
pub files: Vec<QueryFile>,
pub game_versions: Vec<String>,
pub loaders: Vec<String>,
pub accepted: bool,
pub featured: bool,
}

View File

@ -113,7 +113,7 @@ pub async fn init(
"https://github.com/login/oauth/authorize?client_id={}&state={}&scope={}",
client_id,
to_base62(state.0 as u64),
"read%3Auser%20user%3Aemail"
"read%3Auser"
);
Ok(HttpResponse::TemporaryRedirect()

View File

@ -81,11 +81,7 @@ pub fn teams_config(cfg: &mut web::ServiceConfig) {
}
pub fn moderation_config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("moderation")
.service(moderation::mods)
.service(moderation::versions),
);
cfg.service(web::scope("moderation").service(moderation::mods));
}
#[derive(thiserror::Error, Debug)]

View File

@ -493,6 +493,8 @@ async fn mod_create_inner(
donation_urls.push(models::mod_item::DonationUrl {
mod_id: mod_id.into(),
platform_id,
platform_short: "".to_string(),
platform_name: "".to_string(),
url: url.url.clone(),
})
}

View File

@ -1,8 +1,7 @@
use super::ApiError;
use crate::auth::check_is_moderator_from_headers;
use crate::database;
use crate::models;
use crate::models::mods::{ModId, ModStatus, VersionType};
use crate::models::mods::{ModId, ModStatus};
use crate::models::teams::TeamId;
use actix_web::{get, web, HttpRequest, HttpResponse};
use serde::{Deserialize, Serialize};
@ -103,50 +102,3 @@ pub async fn mods(
Ok(HttpResponse::Ok().json(mods))
}
/// Returns a list of versions that need to be approved
#[get("versions")]
pub async fn versions(
req: HttpRequest,
pool: web::Data<PgPool>,
count: web::Query<ResultCount>,
) -> Result<HttpResponse, ApiError> {
check_is_moderator_from_headers(req.headers(), &**pool).await?;
use futures::stream::TryStreamExt;
let versions = sqlx::query!(
"
SELECT * FROM versions
WHERE accepted = FALSE
ORDER BY date_published ASC
LIMIT $1;
",
count.count as i64
)
.fetch_many(&**pool)
.try_filter_map(|e| async {
Ok(e.right().map(|m| models::mods::Version {
id: database::models::ids::VersionId(m.id).into(),
mod_id: database::models::ids::ModId(m.mod_id).into(),
author_id: database::models::ids::UserId(m.author_id).into(),
featured: m.featured,
name: m.name,
version_number: m.version_number,
changelog: m.changelog,
changelog_url: m.changelog_url,
date_published: m.date_published,
downloads: m.downloads as u32,
version_type: VersionType::Release,
files: vec![],
dependencies: vec![],
game_versions: vec![],
loaders: vec![],
}))
})
.try_collect::<Vec<models::mods::Version>>()
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?;
Ok(HttpResponse::Ok().json(versions))
}

View File

@ -223,7 +223,16 @@ fn convert_mod(data: database::models::mod_item::QueryMod) -> models::mods::Mod
source_url: m.source_url,
wiki_url: m.wiki_url,
discord_url: m.discord_url,
donation_urls: None,
donation_urls: Some(
data.donation_urls
.into_iter()
.map(|d| DonationLink {
id: d.platform_short,
platform: d.platform_name,
url: d.url,
})
.collect(),
),
}
}

View File

@ -49,8 +49,7 @@ pub fn check_version(version: &InitialVersionData) -> Result<(), CreateError> {
version
.file_parts
.iter()
.map(|f| check_length(1..=256, "file part name", f))
.collect::<Result<_, _>>()?;
.try_for_each(|f| check_length(1..=256, "file part name", f))?;
check_length(1..=64, "version number", &version.version_number)?;
check_length(3..=256, "version title", &version.version_title)?;
@ -61,13 +60,11 @@ pub fn check_version(version: &InitialVersionData) -> Result<(), CreateError> {
version
.game_versions
.iter()
.map(|v| check_length(1..=256, "game version", &v.0))
.collect::<Result<_, _>>()?;
.try_for_each(|v| check_length(1..=256, "game version", &v.0))?;
version
.loaders
.iter()
.map(|l| check_length(1..=256, "loader name", &l.0))
.collect::<Result<_, _>>()?;
.try_for_each(|l| check_length(1..=256, "loader name", &l.0))?;
Ok(())
}

View File

@ -54,7 +54,6 @@ pub struct VersionIds {
#[get("versions")]
pub async fn versions_get(
req: HttpRequest,
web::Query(ids): web::Query<VersionIds>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
@ -66,39 +65,11 @@ pub async fn versions_get(
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
let mut versions = Vec::new();
for version_data in versions_data {
if let Some(version) = version_data {
let mut authorized = version.accepted;
if let Some(user) = &user_option {
if !authorized {
if user.role.is_mod() {
authorized = true;
} else {
let user_id: database::models::ids::UserId = user.id.into();
let member_exists = sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM team_members tm INNER JOIN mods m ON m.team_id = tm.team_id AND m.id = $1 WHERE tm.user_id = $2)",
version.mod_id as database::models::ModId,
user_id as database::models::ids::UserId,
)
.fetch_one(&**pool)
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?
.exists;
authorized = member_exists.unwrap_or(false);
}
}
}
if authorized {
versions.push(convert_version(version));
}
versions.push(convert_version(version));
}
}
@ -107,7 +78,6 @@ pub async fn versions_get(
#[get("{version_id}")]
pub async fn version_get(
req: HttpRequest,
info: web::Path<(models::ids::VersionId,)>,
pool: web::Data<PgPool>,
) -> Result<HttpResponse, ApiError> {
@ -115,33 +85,8 @@ pub async fn version_get(
let version_data = database::models::Version::get_full(id.into(), &**pool)
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
if let Some(data) = version_data {
if !data.accepted {
if let Some(user) = user_option {
if !user.role.is_mod() {
let user_id: database::models::ids::UserId = user.id.into();
let member_exists = sqlx::query!(
"SELECT EXISTS(SELECT 1 FROM team_members tm INNER JOIN mods m ON m.team_id = tm.team_id AND m.id = $1 WHERE tm.user_id = $2)",
data.mod_id as database::models::ModId,
user_id as database::models::ids::UserId,
)
.fetch_one(&**pool)
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?
.exists;
if !member_exists.unwrap_or(false) {
return Ok(HttpResponse::NotFound().body(""));
}
}
} else {
return Ok(HttpResponse::NotFound().body(""));
}
}
Ok(HttpResponse::Ok().json(convert_version(data)))
} else {
Ok(HttpResponse::NotFound().body(""))
@ -212,7 +157,6 @@ pub struct EditVersion {
pub dependencies: Option<Vec<models::ids::VersionId>>,
pub game_versions: Option<Vec<models::mods::GameVersion>>,
pub loaders: Option<Vec<models::mods::ModLoader>>,
pub accepted: Option<bool>,
pub featured: Option<bool>,
pub primary_file: Option<(String, String)>,
}
@ -262,28 +206,6 @@ pub async fn version_edit(
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?;
if let Some(accepted) = &new_version.accepted {
if !user.role.is_mod() {
return Err(ApiError::CustomAuthenticationError(
"You do not have the permissions to edit the approval of this version!"
.to_string(),
));
}
sqlx::query!(
"
UPDATE versions
SET accepted = $1
WHERE (id = $2)
",
accepted,
id as database::models::ids::VersionId,
)
.execute(&mut *transaction)
.await
.map_err(|e| ApiError::DatabaseError(e.into()))?;
}
if let Some(name) = &new_version.name {
sqlx::query!(
"

View File

@ -2,6 +2,7 @@ use futures::{StreamExt, TryStreamExt};
use log::info;
use super::IndexingError;
use crate::models::mods::SideType;
use crate::search::UploadSearchMod;
use sqlx::postgres::PgPool;
use std::borrow::Cow;
@ -14,7 +15,7 @@ pub async fn index_local(pool: PgPool) -> Result<Vec<UploadSearchMod>, IndexingE
let mut mods = sqlx::query!(
"
SELECT m.id, m.title, m.description, m.downloads, m.icon_url, m.body_url, m.published, m.updated, m.team_id, m.status, m.slug FROM mods m
SELECT m.id, m.title, m.description, m.downloads, m.icon_url, m.body_url, m.published, m.updated, m.team_id, m.status, m.slug, m.license, m.client_side, m.server_side FROM mods m
"
).fetch(&pool);
@ -112,6 +113,38 @@ pub async fn index_local(pool: PgPool) -> Result<Vec<UploadSearchMod>, IndexingE
.map(Cow::Owned)
.unwrap_or_else(|| Cow::Borrowed(""));
let client_side = SideType::from_str(
&sqlx::query!(
"
SELECT name FROM side_types
WHERE id = $1
",
mod_data.client_side,
)
.fetch_one(&pool)
.await?
.name,
);
let server_side = SideType::from_str(
&sqlx::query!(
"
SELECT name FROM side_types
WHERE id = $1
",
mod_data.server_side,
)
.fetch_one(&pool)
.await?
.name,
);
let license = crate::database::models::categories::License::get(
crate::database::models::LicenseId(mod_data.license),
&pool,
)
.await?;
docs_to_add.push(UploadSearchMod {
mod_id: format!("local-{}", mod_id),
title: mod_data.title,
@ -128,6 +161,9 @@ pub async fn index_local(pool: PgPool) -> Result<Vec<UploadSearchMod>, IndexingE
date_modified: mod_data.updated,
modified_timestamp: mod_data.updated.timestamp(),
latest_version,
license: license.short,
client_side: client_side.to_string(),
server_side: server_side.to_string(),
host: Cow::Borrowed("modrinth"),
slug: mod_data.slug,
});
@ -143,7 +179,7 @@ pub async fn query_one(
) -> Result<UploadSearchMod, IndexingError> {
let mod_data = sqlx::query!(
"
SELECT m.id, m.title, m.description, m.downloads, m.icon_url, m.body_url, m.published, m.updated, m.team_id, m.slug
SELECT m.id, m.title, m.description, m.downloads, m.icon_url, m.body_url, m.published, m.updated, m.team_id, m.slug, m.license, m.client_side, m.server_side
FROM mods m
WHERE id = $1
",
@ -225,6 +261,38 @@ pub async fn query_one(
.map(Cow::Owned)
.unwrap_or_else(|| Cow::Borrowed(""));
let client_side = SideType::from_str(
&sqlx::query!(
"
SELECT name FROM side_types
WHERE id = $1
",
mod_data.client_side,
)
.fetch_one(&mut *exec)
.await?
.name,
);
let server_side = SideType::from_str(
&sqlx::query!(
"
SELECT name FROM side_types
WHERE id = $1
",
mod_data.server_side,
)
.fetch_one(&mut *exec)
.await?
.name,
);
let license = crate::database::models::categories::License::get(
crate::database::models::LicenseId(mod_data.license),
&mut *exec,
)
.await?;
Ok(UploadSearchMod {
mod_id: format!("local-{}", mod_id),
title: mod_data.title,
@ -241,6 +309,9 @@ pub async fn query_one(
date_modified: mod_data.updated,
modified_timestamp: mod_data.updated.timestamp(),
latest_version,
license: license.short,
client_side: client_side.to_string(),
server_side: server_side.to_string(),
host: Cow::Borrowed("modrinth"),
slug: mod_data.slug,
})

View File

@ -15,14 +15,14 @@ use thiserror::Error;
pub enum IndexingError {
#[error("Error while connecting to the MeiliSearch database")]
IndexDBError(#[from] meilisearch_sdk::errors::Error),
#[error("Error while importing mods from CurseForge")]
CurseforgeImportError(#[from] reqwest::Error),
#[error("Error while serializing or deserializing JSON: {0}")]
SerDeError(#[from] serde_json::Error),
#[error("Error while parsing a timestamp: {0}")]
ParseDateError(#[from] chrono::format::ParseError),
#[error("Database Error: {0}")]
DatabaseError(#[from] sqlx::error::Error),
SqlxError(#[from] sqlx::error::Error),
#[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError),
#[error("Environment Error")]
EnvError(#[from] dotenv::Error),
}
@ -268,6 +268,9 @@ fn default_settings() -> Settings {
String::from("categories"),
String::from("host"),
String::from("versions"),
String::from("license"),
String::from("client_side"),
String::from("server_side"),
])
}

View File

@ -73,6 +73,9 @@ pub struct UploadSearchMod {
pub icon_url: String,
pub author_url: String,
pub latest_version: Cow<'static, str>,
pub license: String,
pub client_side: String,
pub server_side: String,
/// RFC 3339 formatted creation date of the mod
pub date_created: DateTime<Utc>,
@ -113,6 +116,9 @@ pub struct ResultSearchMod {
/// RFC 3339 formatted modification date of the mod
pub date_modified: String,
pub latest_version: String,
pub license: String,
pub client_side: String,
pub server_side: String,
/// The host of the mod: Either `modrinth` or `curseforge`
pub host: String,