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
+72 -2
View File
@@ -5,11 +5,39 @@ use crate::database::models::version_item::VersionQueryResult;
use crate::database::models::{DBCollection, DBOrganization, DBTeamMember};
use crate::database::redis::RedisPool;
use crate::database::{DBProject, DBVersion, models};
use crate::models::ids::FileId;
use crate::models::projects::{
MissingAttributionFile, OverrideSource, Version,
};
use crate::models::users::User;
use crate::queue::file_scan::{
get_dependency_attributions, get_files_missing_attribution,
};
use crate::routes::ApiError;
use futures::TryStreamExt;
use itertools::Itertools;
pub async fn enrich_dependency_attributions(
versions: &mut [VersionQueryResult],
pool: &PgPool,
) {
let version_ids = versions.iter().map(|v| v.inner.id).collect::<Vec<_>>();
let dep_attr = get_dependency_attributions(pool, &version_ids)
.await
.unwrap_or_default();
for version in versions {
for dep in &mut version.dependencies {
if let Some(attr) = dep_attr.get(&dep.id)
&& (attr.attribution.flame_project.is_some()
|| attr.attribution.resolution.is_some())
{
dep.attribution = Some(attr.attribution.clone());
}
}
}
}
pub trait ValidateAuthorized {
fn validate_authorized(
&self,
@@ -204,7 +232,42 @@ pub async fn filter_visible_versions(
)
.await?;
versions.retain(|x| filtered_version_ids.contains(&x.inner.id));
Ok(versions.into_iter().map(|x| x.into()).collect())
let version_ids: Vec<_> = versions.iter().map(|v| v.inner.id).collect();
let missing = get_files_missing_attribution(pool, &version_ids)
.await
.unwrap_or_default();
enrich_dependency_attributions(&mut versions, pool).await;
Ok(versions
.into_iter()
.map(|v| {
let files_missing = missing
.get(&v.inner.id)
.map(|entries| {
entries
.iter()
.map(|(id, fp)| MissingAttributionFile {
id: FileId(id.0 as u64),
override_source: fp
.as_ref()
.map(|p| OverrideSource::Flame {
id: p.id,
title: p.title.clone(),
url: p.url.clone(),
icon_url: p.icon_url.clone(),
})
.or(Some(OverrideSource::Unknown)),
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let mut version = Version::from(v);
version.files_missing_attribution = files_missing;
version
})
.collect())
}
impl ValidateAuthorized for models::DBOAuthClient {
@@ -258,13 +321,20 @@ pub async fn filter_visible_version_ids(
filter_enlisted_version_ids(versions.clone(), user_option, pool, redis)
.await?;
let version_ids: Vec<_> = versions.iter().map(|v| v.id).collect();
let withheld_versions = get_files_missing_attribution(pool, &version_ids)
.await
.unwrap_or_default();
// Return versions that are not hidden, we are a mod of, or we are enlisted on the team of
for version in versions {
let is_withheld = withheld_versions.contains_key(&version.id);
// We can see the version if:
// - it's not hidden and we can see the project
// - it's not hidden and we can see the project and it's not withheld for attribution
// - we are a mod
// - we are enlisted on the team of the mod
if (!version.status.is_hidden()
&& !is_withheld
&& visible_project_ids.contains(&version.project_id))
|| user_option.as_ref().is_some_and(|x| x.role.is_mod())
|| enlisted_version_ids.contains(&version.id)