feat: new modpack permissions system (#6005)

* Begin external projects moderator database frontend

* add copy link button

* begin project page permissions settings

* MEL database backend routes

* include filename in external files

* wip: when uploading a version file, fetch its overrides as a list

* wip: override license checks

* improve FileHost ref counting

* file host read capability

* scan files when inserting version file

* add dependency sha1 field

* clean up version files

* wip: attributions

* update s3 file host

* attribution scanning basic works

* works

* insert attribution info after resolving

* add routes

* remove dep sha1 stuff

* prepr

* wip: override file sources

* add files_missing_attributions to versions

* return extended version info + attributed at/by

* hook up frontend to backend (mostly)

* expose version date published

* withholding version visibility

* frontend work

* prepr

* use api-client for img upload

* moar frontend

* prepr

* Add schema to attribution resolution and Flame project results

* sqlx prepare

* changes

* remove feature flag, fix optional proof images

* fix schema

* fmt

* fix deletion and file fetch

* prepare

* fix admonition

* update frontend stuff to new schema

* prepr

* attribution on dependencies

* fixes

* sqlx prepare

* fixes

* routes

* fix routes

* Version grandfathering

* prepare

* wip: bulk routes

* pushing what i've got rn

* include link in NoPermission

* change hash insert to bulk route

* query flame even if entry in MEL

* delete file with weird name

* Prioritise putting override files in existing groups even with ExternalLicense

* fix how hex bytes are handled in route

* feat: coolbot moderation changes (#6215)

* Update moderator checklist

* move permissions stage order

* Updated nagContext.versions to v3, added nag for permissions

* Update permissions.vue default messages

* prepr

---------

Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>

* QA

* prepr

* should group by project

* return attribution resolution correctly

* updated by moderator info

* Track what moderator reviewed an attribution moderation status

* default deser FMA field

* new version page

* clean up fetching + add a couple missing features

* qa items

* prepr

* provide moderation package stuff with DI

* format?

* don't redact moderated_at

* move supplementary resources

* Reorganize moderation messages.

* Quick replies for external content permissions.

* prepare

* QA

* allow exempting projects

* Ignore Flame projects which 404

* fix ci

* fix cross project attribution stuff

* Fix permission error

* change what files get cscanned

* add more logging

* QA Jun 22

* fix

* idempotency

* Expose route for rescanning

* update blog link

---------

Co-authored-by: aecsocket <aecsocket@tutanota.com>
Co-authored-by: coolbot100s <76798835+coolbot100s@users.noreply.github.com>
Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
This commit is contained in:
Prospector
2026-06-23 21:27:51 +02:00
committed by GitHub
co-authored by coolbot100s aecsocket aecsocket
parent a686a93858
commit e7926083fb
315 changed files with 11106 additions and 2209 deletions
+1
View File
@@ -1,5 +1,6 @@
use ariadne::ids::base62_id;
base62_id!(AttributionGroupId);
base62_id!(ChargeId);
base62_id!(CampaignDonationId);
base62_id!(CollectionId);
+110
View File
@@ -12,6 +12,7 @@ use ariadne::ids::UserId;
use chrono::{DateTime, Utc};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use url::Url;
use validator::Validate;
/// A project returned from the API
@@ -645,6 +646,98 @@ impl SideTypesMigrationReviewStatus {
}
}
#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
pub struct MissingAttributionFile {
pub id: FileId,
pub override_source: Option<OverrideSource>,
}
#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum OverrideSource {
Flame {
id: u32,
title: String,
url: String,
icon_url: String,
},
Unknown,
}
#[derive(
Debug, Serialize, Deserialize, Clone, PartialEq, Eq, utoipa::ToSchema,
)]
pub struct FlameProject {
pub id: u32,
pub title: String,
pub url: String,
pub icon_url: String,
}
#[derive(
Debug, Serialize, Deserialize, Clone, PartialEq, Eq, utoipa::ToSchema,
)]
#[serde(untagged)]
pub enum AttributionLicense {
Spdx(String),
Custom { name: String },
}
#[derive(
Debug, Serialize, Deserialize, Clone, PartialEq, Eq, utoipa::ToSchema,
)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AttributionResolutionKind {
License {
license: AttributionLicense,
link_to_work: Url,
},
GloballyAllowed {
link_to_work: Url,
},
MyProject {
license: AttributionLicense,
},
SpecialPermissions {
link_to_work: Url,
},
NoPermission {
link_to_work: Option<Url>,
},
}
#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AttributionModerationStatusKind {
NotAllowed,
Approved,
BadProof,
}
#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
pub struct AttributionModerationStatus {
#[serde(flatten)]
pub kind: AttributionModerationStatusKind,
#[serde(default)]
pub reason: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub moderated_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub moderated_by: Option<UserId>,
}
#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
pub struct AttributionResolution {
#[serde(flatten)]
pub kind: AttributionResolutionKind,
#[serde(default)]
pub moderation_status: Option<AttributionModerationStatus>,
#[serde(default)]
pub updated_by_moderator: bool,
pub notes: String,
pub image_urls: Vec<Url>,
}
/// A specific version of a project
#[derive(Debug, Serialize, Deserialize, Clone, utoipa::ToSchema)]
pub struct Version {
@@ -681,6 +774,9 @@ pub struct Version {
/// A list of files available for download for this version.
pub files: Vec<VersionFile>,
/// Files in this version that contain override files not yet attributed.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub files_missing_attribution: Vec<MissingAttributionFile>,
/// A list of projects that this version depends on.
pub dependencies: Vec<Dependency>,
@@ -757,6 +853,7 @@ impl From<VersionQueryResult> for Version {
dependency_type: DependencyType::from_string(
d.dependency_type.as_str(),
),
attribution: d.attribution,
})
.collect(),
loaders: data.loaders.into_iter().map(Loader).collect(),
@@ -768,6 +865,7 @@ impl From<VersionQueryResult> for Version {
.map(|vf| (vf.field_name, vf.value.serialize_internal()))
.collect(),
components: data.components,
files_missing_attribution: Vec::new(),
}
}
}
@@ -899,6 +997,18 @@ pub struct Dependency {
pub file_name: Option<String>,
/// The type of the dependency
pub dependency_type: DependencyType,
#[serde(skip_serializing_if = "Option::is_none")]
pub attribution: Option<DependencyAttribution>,
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, utoipa::ToSchema,
)]
pub struct DependencyAttribution {
#[serde(skip_serializing_if = "Option::is_none")]
pub flame_project: Option<FlameProject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<AttributionResolutionKind>,
}
#[derive(