You've already forked AstralRinth
forked from didirus/AstralRinth
Move to SPDX licenses (#449)
* Move to SPDX licenses Found a way to do this without breaking API compat, so here it is, instead of waiting for v3 Resolves MOD-129 Resolves #396 * License URL updates * what was I thinking * Do a thing * Add open source filter * Remove dead imports * Borrow * Update 20220910132835_spdx-licenses.sql * Add license text route * Update migration * Address comments
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -1566,6 +1566,7 @@ dependencies = [
|
|||||||
"serde_with",
|
"serde_with",
|
||||||
"sha1 0.6.1",
|
"sha1 0.6.1",
|
||||||
"sha2 0.9.9",
|
"sha2 0.9.9",
|
||||||
|
"spdx",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2781,6 +2782,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spdx"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a346909b3fd07776f9b96b98d4a58e3666f831c9a672c279b10f795a34c36425"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ itertools = "0.10.5"
|
|||||||
validator = { version = "0.16.0", features = ["derive", "phone"] }
|
validator = { version = "0.16.0", features = ["derive", "phone"] }
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
censor = "0.2.0"
|
censor = "0.2.0"
|
||||||
|
spdx = { version = "0.9.0", features = ["text"] }
|
||||||
|
|
||||||
dotenvy = "0.15.6"
|
dotenvy = "0.15.6"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
|||||||
27
migrations/20221126222222_spdx-licenses.sql
Normal file
27
migrations/20221126222222_spdx-licenses.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
ALTER TABLE mods ADD COLUMN license_new varchar(2048) DEFAULT 'LicenseRef-All-Rights-Reserved' NOT NULL;
|
||||||
|
|
||||||
|
UPDATE mods SET license_new = licenses.short FROM licenses WHERE mods.license = licenses.id;
|
||||||
|
|
||||||
|
UPDATE mods SET license_new = 'LicenseRef-Custom' WHERE license_new = 'custom';
|
||||||
|
UPDATE mods SET license_new = 'LicenseRef-All-Rights-Reserved' WHERE license_new = 'arr';
|
||||||
|
UPDATE mods SET license_new = 'Apache-2.0' WHERE license_new = 'apache';
|
||||||
|
UPDATE mods SET license_new = 'BSD-2-Clause' WHERE license_new = 'bsd-2-clause';
|
||||||
|
UPDATE mods SET license_new = 'BSD-3-Clause' WHERE license_new = 'bsd-3-clause' OR license_new = 'bsd';
|
||||||
|
UPDATE mods SET license_new = 'CC0-1.0' WHERE license_new = 'cc0';
|
||||||
|
UPDATE mods SET license_new = 'Unlicense' WHERE license_new = 'unlicense';
|
||||||
|
UPDATE mods SET license_new = 'MIT' WHERE license_new = 'mit';
|
||||||
|
UPDATE mods SET license_new = 'LGPL-3.0-only' WHERE license_new = 'lgpl-3';
|
||||||
|
UPDATE mods SET license_new = 'LGPL-2.1-only' WHERE license_new = 'lgpl-2.1' OR license_new = 'lgpl';
|
||||||
|
UPDATE mods SET license_new = 'MPL-2.0' WHERE license_new = 'mpl-2';
|
||||||
|
UPDATE mods SET license_new = 'ISC' WHERE license_new = 'isc';
|
||||||
|
UPDATE mods SET license_new = 'Zlib' WHERE license_new = 'zlib';
|
||||||
|
UPDATE mods SET license_new = 'GPL-2.0-only' WHERE license_new = 'gpl-2';
|
||||||
|
UPDATE mods SET license_new = 'GPL-3.0-only' WHERE license_new = 'gpl-3';
|
||||||
|
UPDATE mods SET license_new = 'AGPL-3.0-only' WHERE license_new = 'agpl';
|
||||||
|
|
||||||
|
UPDATE mods SET license_url = NULL WHERE license_url LIKE 'https://cdn.modrinth.com/licenses/%';
|
||||||
|
|
||||||
|
ALTER TABLE mods DROP COLUMN license;
|
||||||
|
ALTER TABLE mods RENAME COLUMN license_new TO license;
|
||||||
|
|
||||||
|
DROP TABLE licenses;
|
||||||
1339
sqlx-data.json
1339
sqlx-data.json
File diff suppressed because it is too large
Load Diff
@@ -38,12 +38,6 @@ pub struct ReportType {
|
|||||||
pub report_type: String,
|
pub report_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct License {
|
|
||||||
pub id: LicenseId,
|
|
||||||
pub short: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DonationPlatform {
|
pub struct DonationPlatform {
|
||||||
pub id: DonationPlatformId,
|
pub id: DonationPlatformId,
|
||||||
pub short: String,
|
pub short: String,
|
||||||
@@ -685,157 +679,6 @@ impl<'a> GameVersionBuilder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct LicenseBuilder<'a> {
|
|
||||||
pub short: Option<&'a str>,
|
|
||||||
pub name: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl License {
|
|
||||||
pub fn builder() -> LicenseBuilder<'static> {
|
|
||||||
LicenseBuilder::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_id<'a, E>(
|
|
||||||
id: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<LicenseId>, DatabaseError>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT id FROM licenses
|
|
||||||
WHERE short = $1
|
|
||||||
",
|
|
||||||
id
|
|
||||||
)
|
|
||||||
.fetch_optional(exec)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(result.map(|r| LicenseId(r.id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get<'a, E>(
|
|
||||||
id: LicenseId,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<License, DatabaseError>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT short, name FROM licenses
|
|
||||||
WHERE id = $1
|
|
||||||
",
|
|
||||||
id as LicenseId
|
|
||||||
)
|
|
||||||
.fetch_one(exec)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(License {
|
|
||||||
id,
|
|
||||||
short: result.short,
|
|
||||||
name: result.name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list<'a, E>(exec: E) -> Result<Vec<License>, DatabaseError>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT id, short, name FROM licenses
|
|
||||||
"
|
|
||||||
)
|
|
||||||
.fetch_many(exec)
|
|
||||||
.try_filter_map(|e| async {
|
|
||||||
Ok(e.right().map(|c| License {
|
|
||||||
id: LicenseId(c.id),
|
|
||||||
short: c.short,
|
|
||||||
name: c.name,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.try_collect::<Vec<License>>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn remove<'a, E>(
|
|
||||||
short: &str,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<Option<()>, DatabaseError>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"
|
|
||||||
DELETE FROM licenses
|
|
||||||
WHERE short = $1
|
|
||||||
",
|
|
||||||
short
|
|
||||||
)
|
|
||||||
.execute(exec)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if result.rows_affected() == 0 {
|
|
||||||
// Nothing was deleted
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> LicenseBuilder<'a> {
|
|
||||||
/// The license's short name/abbreviation. Spaces must be replaced with '_' for it to be valid
|
|
||||||
pub fn short(
|
|
||||||
self,
|
|
||||||
short: &'a str,
|
|
||||||
) -> Result<LicenseBuilder<'a>, DatabaseError> {
|
|
||||||
Ok(Self {
|
|
||||||
short: Some(short),
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The license's long name
|
|
||||||
pub fn name(
|
|
||||||
self,
|
|
||||||
name: &'a str,
|
|
||||||
) -> Result<LicenseBuilder<'a>, DatabaseError> {
|
|
||||||
Ok(Self {
|
|
||||||
name: Some(name),
|
|
||||||
..self
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn insert<'b, E>(
|
|
||||||
self,
|
|
||||||
exec: E,
|
|
||||||
) -> Result<LicenseId, DatabaseError>
|
|
||||||
where
|
|
||||||
E: sqlx::Executor<'b, Database = sqlx::Postgres>,
|
|
||||||
{
|
|
||||||
let result = sqlx::query!(
|
|
||||||
"
|
|
||||||
INSERT INTO licenses (short, name)
|
|
||||||
VALUES ($1, $2)
|
|
||||||
ON CONFLICT (short) DO NOTHING
|
|
||||||
RETURNING id
|
|
||||||
",
|
|
||||||
self.short,
|
|
||||||
self.name,
|
|
||||||
)
|
|
||||||
.fetch_one(exec)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(LicenseId(result.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DonationPlatformBuilder<'a> {
|
pub struct DonationPlatformBuilder<'a> {
|
||||||
pub short: Option<&'a str>,
|
pub short: Option<&'a str>,
|
||||||
|
|||||||
@@ -131,9 +131,6 @@ pub struct StatusId(pub i32);
|
|||||||
pub struct SideTypeId(pub i32);
|
pub struct SideTypeId(pub i32);
|
||||||
#[derive(Copy, Clone, Debug, Type)]
|
#[derive(Copy, Clone, Debug, Type)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct LicenseId(pub i32);
|
|
||||||
#[derive(Copy, Clone, Debug, Type)]
|
|
||||||
#[sqlx(transparent)]
|
|
||||||
pub struct DonationPlatformId(pub i32);
|
pub struct DonationPlatformId(pub i32);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Type, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, Type, PartialEq, Eq, Hash)]
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ pub struct ProjectBuilder {
|
|||||||
pub status: StatusId,
|
pub status: StatusId,
|
||||||
pub client_side: SideTypeId,
|
pub client_side: SideTypeId,
|
||||||
pub server_side: SideTypeId,
|
pub server_side: SideTypeId,
|
||||||
pub license: LicenseId,
|
pub license: String,
|
||||||
pub slug: Option<String>,
|
pub slug: Option<String>,
|
||||||
pub donation_urls: Vec<DonationUrl>,
|
pub donation_urls: Vec<DonationUrl>,
|
||||||
pub gallery_items: Vec<GalleryItem>,
|
pub gallery_items: Vec<GalleryItem>,
|
||||||
@@ -201,7 +201,7 @@ pub struct Project {
|
|||||||
pub discord_url: Option<String>,
|
pub discord_url: Option<String>,
|
||||||
pub client_side: SideTypeId,
|
pub client_side: SideTypeId,
|
||||||
pub server_side: SideTypeId,
|
pub server_side: SideTypeId,
|
||||||
pub license: LicenseId,
|
pub license: String,
|
||||||
pub slug: Option<String>,
|
pub slug: Option<String>,
|
||||||
pub moderation_message: Option<String>,
|
pub moderation_message: Option<String>,
|
||||||
pub moderation_message_body: Option<String>,
|
pub moderation_message_body: Option<String>,
|
||||||
@@ -247,7 +247,7 @@ impl Project {
|
|||||||
self.client_side as SideTypeId,
|
self.client_side as SideTypeId,
|
||||||
self.server_side as SideTypeId,
|
self.server_side as SideTypeId,
|
||||||
self.license_url.as_ref(),
|
self.license_url.as_ref(),
|
||||||
self.license as LicenseId,
|
&self.license,
|
||||||
self.slug.as_ref(),
|
self.slug.as_ref(),
|
||||||
self.project_type as ProjectTypeId
|
self.project_type as ProjectTypeId
|
||||||
)
|
)
|
||||||
@@ -301,7 +301,7 @@ impl Project {
|
|||||||
client_side: SideTypeId(row.client_side),
|
client_side: SideTypeId(row.client_side),
|
||||||
status: StatusId(row.status),
|
status: StatusId(row.status),
|
||||||
server_side: SideTypeId(row.server_side),
|
server_side: SideTypeId(row.server_side),
|
||||||
license: LicenseId(row.license),
|
license: row.license,
|
||||||
slug: row.slug,
|
slug: row.slug,
|
||||||
body: row.body,
|
body: row.body,
|
||||||
follows: row.follows,
|
follows: row.follows,
|
||||||
@@ -362,7 +362,7 @@ impl Project {
|
|||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: StatusId(m.status),
|
status: StatusId(m.status),
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: LicenseId(m.license),
|
license: m.license,
|
||||||
slug: m.slug,
|
slug: m.slug,
|
||||||
body: m.body,
|
body: m.body,
|
||||||
follows: m.follows,
|
follows: m.follows,
|
||||||
@@ -649,7 +649,7 @@ impl Project {
|
|||||||
m.updated updated, m.approved approved, 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.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
s.status status_name, 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,
|
||||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
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 mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||||
@@ -659,7 +659,6 @@ impl Project {
|
|||||||
INNER JOIN statuses s ON s.id = m.status
|
INNER JOIN statuses s ON s.id = m.status
|
||||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
INNER JOIN licenses l ON m.license = l.id
|
|
||||||
LEFT JOIN mods_donations md ON md.joining_mod_id = m.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 donation_platforms dp ON md.joining_platform_id = dp.id
|
||||||
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
||||||
@@ -667,7 +666,7 @@ impl Project {
|
|||||||
LEFT JOIN versions v ON v.mod_id = m.id
|
LEFT JOIN versions v ON v.mod_id = m.id
|
||||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
WHERE m.id = $1
|
WHERE m.id = $1
|
||||||
GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;
|
GROUP BY pt.id, s.id, cs.id, ss.id, m.id;
|
||||||
",
|
",
|
||||||
id as ProjectId,
|
id as ProjectId,
|
||||||
)
|
)
|
||||||
@@ -712,7 +711,7 @@ impl Project {
|
|||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: StatusId(m.status),
|
status: StatusId(m.status),
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: LicenseId(m.license),
|
license: m.license.clone(),
|
||||||
slug: m.slug.clone(),
|
slug: m.slug.clone(),
|
||||||
body: m.body.clone(),
|
body: m.body.clone(),
|
||||||
follows: m.follows,
|
follows: m.follows,
|
||||||
@@ -806,8 +805,6 @@ impl Project {
|
|||||||
status: crate::models::projects::ProjectStatus::from_str(
|
status: crate::models::projects::ProjectStatus::from_str(
|
||||||
&m.status_name,
|
&m.status_name,
|
||||||
),
|
),
|
||||||
license_id: m.short,
|
|
||||||
license_name: m.license_name,
|
|
||||||
client_side: crate::models::projects::SideType::from_str(
|
client_side: crate::models::projects::SideType::from_str(
|
||||||
&m.client_side_type,
|
&m.client_side_type,
|
||||||
),
|
),
|
||||||
@@ -838,7 +835,7 @@ impl Project {
|
|||||||
m.updated updated, m.approved approved, 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.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
s.status status_name, 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,
|
||||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
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 mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||||
@@ -848,7 +845,6 @@ impl Project {
|
|||||||
INNER JOIN statuses s ON s.id = m.status
|
INNER JOIN statuses s ON s.id = m.status
|
||||||
INNER JOIN side_types cs ON m.client_side = cs.id
|
INNER JOIN side_types cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
INNER JOIN licenses l ON m.license = l.id
|
|
||||||
LEFT JOIN mods_donations md ON md.joining_mod_id = m.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 donation_platforms dp ON md.joining_platform_id = dp.id
|
||||||
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id
|
||||||
@@ -856,7 +852,7 @@ impl Project {
|
|||||||
LEFT JOIN versions v ON v.mod_id = m.id
|
LEFT JOIN versions v ON v.mod_id = m.id
|
||||||
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
LEFT JOIN mods_gallery mg ON mg.mod_id = m.id
|
||||||
WHERE m.id = ANY($1)
|
WHERE m.id = ANY($1)
|
||||||
GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;
|
GROUP BY pt.id, s.id, cs.id, ss.id, m.id;
|
||||||
",
|
",
|
||||||
&project_ids_parsed
|
&project_ids_parsed
|
||||||
)
|
)
|
||||||
@@ -903,7 +899,7 @@ impl Project {
|
|||||||
client_side: SideTypeId(m.client_side),
|
client_side: SideTypeId(m.client_side),
|
||||||
status: StatusId(m.status),
|
status: StatusId(m.status),
|
||||||
server_side: SideTypeId(m.server_side),
|
server_side: SideTypeId(m.server_side),
|
||||||
license: LicenseId(m.license),
|
license: m.license.clone(),
|
||||||
slug: m.slug.clone(),
|
slug: m.slug.clone(),
|
||||||
body: m.body.clone(),
|
body: m.body.clone(),
|
||||||
follows: m.follows,
|
follows: m.follows,
|
||||||
@@ -983,8 +979,6 @@ impl Project {
|
|||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
|
status: crate::models::projects::ProjectStatus::from_str(&m.status_name),
|
||||||
license_id: m.short,
|
|
||||||
license_name: m.license_name,
|
|
||||||
client_side: crate::models::projects::SideType::from_str(&m.client_side_type),
|
client_side: crate::models::projects::SideType::from_str(&m.client_side_type),
|
||||||
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
|
server_side: crate::models::projects::SideType::from_str(&m.server_side_type),
|
||||||
}}))
|
}}))
|
||||||
@@ -1004,8 +998,6 @@ pub struct QueryProject {
|
|||||||
pub donation_urls: Vec<DonationUrl>,
|
pub donation_urls: Vec<DonationUrl>,
|
||||||
pub gallery_items: Vec<GalleryItem>,
|
pub gallery_items: Vec<GalleryItem>,
|
||||||
pub status: crate::models::projects::ProjectStatus,
|
pub status: crate::models::projects::ProjectStatus,
|
||||||
pub license_id: String,
|
|
||||||
pub license_name: String,
|
|
||||||
pub client_side: crate::models::projects::SideType,
|
pub client_side: crate::models::projects::SideType,
|
||||||
pub server_side: crate::models::projects::SideType,
|
pub server_side: crate::models::projects::SideType,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,8 +121,24 @@ impl From<QueryProject> for Project {
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
license: License {
|
license: License {
|
||||||
id: data.license_id,
|
id: m.license.clone(),
|
||||||
name: data.license_name,
|
name: match spdx::Expression::parse(&*m.license) {
|
||||||
|
Ok(spdx_expr) => {
|
||||||
|
let mut vec: Vec<&str> = Vec::new();
|
||||||
|
for node in spdx_expr.iter() {
|
||||||
|
if let spdx::expression::ExprNode::Req(req) = node {
|
||||||
|
if let Some(id) = req.req.license.id() {
|
||||||
|
vec.push(id.full_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// spdx crate returns AND/OR operations in postfix order
|
||||||
|
// and it would be a lot more effort to make it actually in order
|
||||||
|
// so let's just ignore that and make them comma-separated
|
||||||
|
vec.join(", ")
|
||||||
|
}
|
||||||
|
Err(_) => "".to_string(),
|
||||||
|
},
|
||||||
url: m.license_url,
|
url: m.license_url,
|
||||||
},
|
},
|
||||||
client_side: data.client_side,
|
client_side: data.client_side,
|
||||||
@@ -215,6 +231,8 @@ impl SideType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_LICENSE_ID: &str = "LicenseRef-All-Rights-Reserved";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct License {
|
pub struct License {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|||||||
@@ -688,16 +688,16 @@ pub async fn project_create_inner(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let license_id = models::categories::License::get_id(
|
let license_id = spdx::Expression::parse(
|
||||||
&project_create_data.license_id,
|
&project_create_data.license_id,
|
||||||
&mut *transaction,
|
|
||||||
)
|
)
|
||||||
.await?
|
.map_err(|err| {
|
||||||
.ok_or_else(|| {
|
CreateError::InvalidInput(format!(
|
||||||
CreateError::InvalidInput(
|
"Invalid SPDX license identifier: {}",
|
||||||
"License specified does not exist.".to_string(),
|
err
|
||||||
)
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut donation_urls = vec![];
|
let mut donation_urls = vec![];
|
||||||
|
|
||||||
if let Some(urls) = &project_create_data.donation_urls {
|
if let Some(urls) = &project_create_data.donation_urls {
|
||||||
@@ -744,7 +744,7 @@ pub async fn project_create_inner(
|
|||||||
status: status_id,
|
status: status_id,
|
||||||
client_side: client_side_id,
|
client_side: client_side_id,
|
||||||
server_side: server_side_id,
|
server_side: server_side_id,
|
||||||
license: license_id,
|
license: license_id.to_string(),
|
||||||
slug: Some(project_create_data.slug),
|
slug: Some(project_create_data.slug),
|
||||||
donation_urls,
|
donation_urls,
|
||||||
gallery_items: gallery_urls
|
gallery_items: gallery_urls
|
||||||
|
|||||||
@@ -857,12 +857,18 @@ pub async fn project_edit(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let license_id = database::models::categories::License::get_id(
|
let mut license = license.clone();
|
||||||
license,
|
|
||||||
&mut *transaction,
|
if license.to_lowercase() == "arr" {
|
||||||
)
|
license = models::projects::DEFAULT_LICENSE_ID.to_string();
|
||||||
.await?
|
}
|
||||||
.expect("No database entry found for license");
|
|
||||||
|
spdx::Expression::parse(&*license).map_err(|err| {
|
||||||
|
ApiError::InvalidInput(format!(
|
||||||
|
"Invalid SPDX license identifier: {}",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
@@ -870,7 +876,7 @@ pub async fn project_edit(
|
|||||||
SET license = $1
|
SET license = $1
|
||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
license_id as database::models::LicenseId,
|
license,
|
||||||
id as database::models::ids::ProjectId,
|
id as database::models::ids::ProjectId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::ApiError;
|
use super::ApiError;
|
||||||
use crate::database::models;
|
use crate::database::models;
|
||||||
use crate::database::models::categories::{
|
use crate::database::models::categories::{
|
||||||
DonationPlatform, License, ProjectType, ReportType,
|
DonationPlatform, ProjectType, ReportType,
|
||||||
};
|
};
|
||||||
use crate::util::auth::check_is_admin_from_headers;
|
use crate::util::auth::check_is_admin_from_headers;
|
||||||
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
|
use actix_web::{delete, get, put, web, HttpRequest, HttpResponse};
|
||||||
@@ -21,9 +21,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(game_version_list)
|
.service(game_version_list)
|
||||||
.service(game_version_create)
|
.service(game_version_create)
|
||||||
.service(game_version_delete)
|
.service(game_version_delete)
|
||||||
.service(license_create)
|
|
||||||
.service(license_delete)
|
|
||||||
.service(license_list)
|
.service(license_list)
|
||||||
|
.service(license_text)
|
||||||
.service(donation_platform_create)
|
.service(donation_platform_create)
|
||||||
.service(donation_platform_list)
|
.service(donation_platform_list)
|
||||||
.service(donation_platform_delete)
|
.service(donation_platform_delete)
|
||||||
@@ -302,75 +301,52 @@ pub async fn game_version_delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct LicenseQueryData {
|
pub struct License {
|
||||||
short: String,
|
short: String,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("license")]
|
#[get("license")]
|
||||||
pub async fn license_list(
|
pub async fn license_list() -> HttpResponse {
|
||||||
pool: web::Data<PgPool>,
|
let licenses = spdx::identifiers::LICENSES;
|
||||||
) -> Result<HttpResponse, ApiError> {
|
let mut results: Vec<License> = Vec::with_capacity(licenses.len());
|
||||||
let results: Vec<LicenseQueryData> = License::list(&**pool)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|x| LicenseQueryData {
|
|
||||||
short: x.short,
|
|
||||||
name: x.name,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Ok(HttpResponse::Ok().json(results))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
for (short, name, _) in licenses {
|
||||||
pub struct LicenseData {
|
results.push(License {
|
||||||
name: String,
|
short: short.to_string(),
|
||||||
}
|
name: name.to_string(),
|
||||||
|
});
|
||||||
#[put("license/{name}")]
|
|
||||||
pub async fn license_create(
|
|
||||||
req: HttpRequest,
|
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
license: web::Path<(String,)>,
|
|
||||||
license_data: web::Json<LicenseData>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
|
||||||
|
|
||||||
let short = license.into_inner().0;
|
|
||||||
|
|
||||||
let _id = License::builder()
|
|
||||||
.short(&short)?
|
|
||||||
.name(&license_data.name)?
|
|
||||||
.insert(&**pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[delete("license/{name}")]
|
|
||||||
pub async fn license_delete(
|
|
||||||
req: HttpRequest,
|
|
||||||
pool: web::Data<PgPool>,
|
|
||||||
license: web::Path<(String,)>,
|
|
||||||
) -> Result<HttpResponse, ApiError> {
|
|
||||||
check_is_admin_from_headers(req.headers(), &**pool).await?;
|
|
||||||
|
|
||||||
let name = license.into_inner().0;
|
|
||||||
let mut transaction =
|
|
||||||
pool.begin().await.map_err(models::DatabaseError::from)?;
|
|
||||||
|
|
||||||
let result = License::remove(&name, &mut transaction).await?;
|
|
||||||
|
|
||||||
transaction
|
|
||||||
.commit()
|
|
||||||
.await
|
|
||||||
.map_err(models::DatabaseError::from)?;
|
|
||||||
|
|
||||||
if result.is_some() {
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
|
||||||
} else {
|
|
||||||
Ok(HttpResponse::NotFound().body(""))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct LicenseText {
|
||||||
|
body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("license/{id}")]
|
||||||
|
pub async fn license_text(
|
||||||
|
params: web::Path<(String,)>,
|
||||||
|
) -> Result<HttpResponse, ApiError> {
|
||||||
|
let license_id = params.into_inner().0;
|
||||||
|
|
||||||
|
if license_id == crate::models::projects::DEFAULT_LICENSE_ID.to_string() {
|
||||||
|
return Ok(HttpResponse::Ok().json(LicenseText {
|
||||||
|
body: "All rights reserved unless explicitly stated.".to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(license) = spdx::license_id(&*license_id) {
|
||||||
|
return Ok(HttpResponse::Ok().json(LicenseText {
|
||||||
|
body: license.text().to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ApiError::InvalidInput(
|
||||||
|
"Invalid SPDX identifier specified".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ pub fn tags_config(cfg: &mut web::ServiceConfig) {
|
|||||||
.service(tags::game_version_list)
|
.service(tags::game_version_list)
|
||||||
.service(super::tags::game_version_create)
|
.service(super::tags::game_version_create)
|
||||||
.service(super::tags::game_version_delete)
|
.service(super::tags::game_version_delete)
|
||||||
.service(super::tags::license_create)
|
|
||||||
.service(super::tags::license_delete)
|
|
||||||
.service(super::tags::license_list)
|
.service(super::tags::license_list)
|
||||||
.service(super::tags::donation_platform_create)
|
.service(super::tags::donation_platform_create)
|
||||||
.service(super::tags::donation_platform_list)
|
.service(super::tags::donation_platform_list)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub async fn index_local(
|
|||||||
SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,
|
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.published published, m.approved approved, m.updated updated,
|
m.icon_url icon_url, m.published published, m.approved approved, m.updated updated,
|
||||||
m.team_id team_id, m.license license, m.slug slug,
|
m.team_id team_id, m.license license, m.slug slug,
|
||||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,
|
s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,
|
||||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||||
ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,
|
ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,
|
||||||
ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,
|
ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,
|
||||||
@@ -34,11 +34,10 @@ pub async fn index_local(
|
|||||||
INNER JOIN project_types pt ON pt.id = m.project_type
|
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 cs ON m.client_side = cs.id
|
||||||
INNER JOIN side_types ss ON m.server_side = ss.id
|
INNER JOIN side_types ss ON m.server_side = ss.id
|
||||||
INNER JOIN licenses l ON m.license = l.id
|
|
||||||
INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE
|
INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE
|
||||||
INNER JOIN users u ON tm.user_id = u.id
|
INNER JOIN users u ON tm.user_id = u.id
|
||||||
WHERE s.status = $1 OR s.status = $2
|
WHERE s.status = $1 OR s.status = $2
|
||||||
GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;
|
GROUP BY m.id, s.id, cs.id, ss.id, pt.id, u.id;
|
||||||
",
|
",
|
||||||
crate::models::projects::ProjectStatus::Approved.as_str(),
|
crate::models::projects::ProjectStatus::Approved.as_str(),
|
||||||
crate::models::projects::ProjectStatus::Archived.as_str(),
|
crate::models::projects::ProjectStatus::Archived.as_str(),
|
||||||
@@ -73,6 +72,16 @@ pub async fn index_local(
|
|||||||
|
|
||||||
let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into();
|
let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into();
|
||||||
|
|
||||||
|
let license = match m.license.split(" ").next() {
|
||||||
|
Some(license) => license.to_string(),
|
||||||
|
None => m.license,
|
||||||
|
};
|
||||||
|
|
||||||
|
let open_source = match spdx::license_id(&license) {
|
||||||
|
Some(id) => id.is_osi_approved(),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
UploadSearchProject {
|
UploadSearchProject {
|
||||||
project_id: format!("{}", project_id),
|
project_id: format!("{}", project_id),
|
||||||
title: m.title,
|
title: m.title,
|
||||||
@@ -88,13 +97,14 @@ pub async fn index_local(
|
|||||||
modified_timestamp: m.updated.timestamp(),
|
modified_timestamp: m.updated.timestamp(),
|
||||||
latest_version: versions.last().cloned().unwrap_or_else(|| "None".to_string()),
|
latest_version: versions.last().cloned().unwrap_or_else(|| "None".to_string()),
|
||||||
versions,
|
versions,
|
||||||
license: m.short,
|
license,
|
||||||
client_side: m.client_side_type,
|
client_side: m.client_side_type,
|
||||||
server_side: m.server_side_type,
|
server_side: m.server_side_type,
|
||||||
slug: m.slug,
|
slug: m.slug,
|
||||||
project_type: m.project_type_name,
|
project_type: m.project_type_name,
|
||||||
gallery: m.gallery.unwrap_or_default(),
|
gallery: m.gallery.unwrap_or_default(),
|
||||||
display_categories
|
display_categories,
|
||||||
|
open_source,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
|||||||
"date_created",
|
"date_created",
|
||||||
"date_modified",
|
"date_modified",
|
||||||
"project_id",
|
"project_id",
|
||||||
|
"open_source",
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
|
const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ pub struct UploadSearchProject {
|
|||||||
pub date_modified: DateTime<Utc>,
|
pub date_modified: DateTime<Utc>,
|
||||||
/// Unix timestamp of the last major modification
|
/// Unix timestamp of the last major modification
|
||||||
pub modified_timestamp: i64,
|
pub modified_timestamp: i64,
|
||||||
|
pub open_source: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|||||||
Reference in New Issue
Block a user