mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 13:16:38 +00:00
perf: Attribution route perf improvements (#6489)
* Attribution route perf improvements * fix clippy * clear exemptions when user attempts to scan their file * Increase Redis TTL/refresh time
This commit is contained in:
@@ -50,7 +50,7 @@ Run these from the **root** folder before opening a pull request - do not run th
|
||||
- **App frontend:** `pnpm prepr:frontend:app`
|
||||
- **Frontend libs:** `pnpm prepr:frontend:lib`
|
||||
- **All frontend (app+web):** `pnpm prepr`
|
||||
- **Labrinth (backend):** See `apps/labrinth/CLAUDE.md`
|
||||
- **Labrinth (backend):** See `apps/labrinth/AGENTS.md`
|
||||
|
||||
The website and app `prepr` commands
|
||||
|
||||
@@ -62,7 +62,7 @@ The website and app `prepr` commands
|
||||
|
||||
## Project-Specific Instructions
|
||||
|
||||
Each project may have its own `CLAUDE.md` with detailed instructions:
|
||||
Each project may have its own file with detailed instructions:
|
||||
|
||||
- [`apps/labrinth/AGENTS.md`](apps/labrinth/AGENTS.md) — Backend API
|
||||
- [`apps/frontend/CLAUDE.md`](apps/frontend/CLAUDE.md) - Frontend Website
|
||||
|
||||
Generated
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tinsert into file_scans (file_id)\n\t\tselect f.id\n\t\tfrom files f\n\t\twhere f.version_id = any($1)\n\t\ton conflict (file_id) do nothing\n\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1206e354ee5be83bca26b802b1e7a7ebdf9ee4ae9a142ee93e89ddc1536e7284"
|
||||
}
|
||||
Generated
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tselect distinct f.version_id as \"version_id: DBVersionId\"\n\t\tfrom override_file_sources ofs\n\t\tinner join files f on f.id = ofs.file_id\n\t\tinner join versions v on v.id = f.version_id\n\t\twhere ofs.sha1 = $1\n\t\t\tand v.mod_id = $2\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version_id: DBVersionId",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Bytea",
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "365d51d5c56e9420618bd23ec2f0624cb261a1aeb1f929277fa063d730c2d99a"
|
||||
}
|
||||
Generated
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tinsert into file_scans (file_id)\n\t\tselect f.id\n\t\tfrom files f\n\t\tinner join attribution_enforced_versions aev on aev.id = f.version_id\n\t\twhere f.version_id = any($1)\n\t\ton conflict (file_id) do nothing\n\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "43ce7cbf44ce123c3d71c5dee035022d2e6b537eb53fcb39608e820e77174b81"
|
||||
}
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tdelete from attributions_exemptions\n\t\twhere version_id = any($1) or project_id = any($2)\n\t\t",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array",
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "70d1e6c703a2124e96e60a3fabe90b0b2dcb0c8631fdf5da57b3bc8047acfef2"
|
||||
}
|
||||
Generated
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n select version_id as \"version_id: DBVersionId\"\n from files\n where id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version_id: DBVersionId",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "752eb66451b57e9595b8a1671b1f98465ddb903a57d566de9e472b0b78ad2d39"
|
||||
}
|
||||
Generated
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n\t\tselect distinct f.version_id as \"version_id: DBVersionId\"\n\t\tfrom project_attribution_files paf\n\t\tinner join project_attribution_groups pag on pag.id = paf.group_id\n\t\tinner join override_file_sources ofs on ofs.sha1 = paf.sha1\n\t\tinner join files f on f.id = ofs.file_id\n\t\tinner join versions v on v.id = f.version_id\n\t\twhere paf.group_id = any($1)\n\t\t\tand pag.project_id = v.mod_id\n\t\t",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "version_id: DBVersionId",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8Array"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a68a8b6e1fb06fac7021657120e8446067fcc40dc1cf7f75670fdf2486b19f41"
|
||||
}
|
||||
@@ -29,6 +29,6 @@
|
||||
- You can force a search reindex by:
|
||||
- Running `cd apps/labrinth && cargo run -p labrinth -- --run-background-task index-search` (prefer this if backend is running locally)
|
||||
- Hitting the force reindex admin endpoint
|
||||
- To seed the database locally: `psql postgresql://labrinth:labrinth@localhost/labrinth -f apps/labrinth/fixtures/labrinth-seed-data-202508052143.sql
|
||||
`
|
||||
- To seed the database locally: `psql postgresql://labrinth:labrinth@localhost/labrinth -f apps/labrinth/fixtures/labrinth-seed-data-202508052143.sql`
|
||||
- When writing `sqlx` queries, prefer `r#` raw strings over escaping quotes
|
||||
- When interacting with the Postgres database, prefer using a `ro_pool: ReadOnlyPgPool` when performing read-only operations
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Labrinth
|
||||
|
||||
Labrinth is the backend API service for Modrinth, written in Rust.
|
||||
|
||||
## Code style
|
||||
|
||||
- When writing `sqlx` queries, NEVER use `query` directly. Always prefer using the `query!`, `query_as!`, `query_scalar!` macros.
|
||||
|
||||
## Pre-PR Checks
|
||||
|
||||
When the user refers to "perform[ing] pre-PR checks", do the following:
|
||||
|
||||
- Run `cargo clippy -p labrinth --all-targets` — there must be ZERO warnings, otherwise CI will fail
|
||||
- DO NOT run tests unless explicitly requested (they take a long time)
|
||||
- Prepare the sqlx cache: cd into `apps/labrinth` and run `cargo sqlx prepare -- --tests`
|
||||
- NEVER run `cargo sqlx prepare --workspace`
|
||||
|
||||
## Testing
|
||||
|
||||
- Run `cargo test -p labrinth --all-targets` to test your changes — all tests must pass
|
||||
|
||||
## Local Services
|
||||
|
||||
- Read the root `docker-compose.yml` to see what running services are available while developing
|
||||
- Use `docker exec` to access these services
|
||||
|
||||
### Clickhouse
|
||||
|
||||
- Access: `docker exec labrinth-clickhouse clickhouse-client`
|
||||
- Database: `staging_ariadne`
|
||||
|
||||
### Postgres
|
||||
|
||||
- Access: `docker exec labrinth-postgres psql -U labrinth -d labrinth -c "<query>"`
|
||||
@@ -10,34 +10,11 @@ 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::queue::file_scan::get_files_missing_attribution;
|
||||
use crate::routes::ApiError;
|
||||
use futures::TryStreamExt;
|
||||
use itertools::Itertools;
|
||||
|
||||
pub async fn enrich_dependency_attributions(
|
||||
versions: &mut [VersionQueryResult],
|
||||
pool: &ReadOnlyPgPool,
|
||||
) {
|
||||
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,
|
||||
@@ -235,12 +212,10 @@ pub async fn filter_visible_versions(
|
||||
versions.retain(|x| filtered_version_ids.contains(&x.inner.id));
|
||||
|
||||
let version_ids: Vec<_> = versions.iter().map(|v| v.inner.id).collect();
|
||||
let missing = get_files_missing_attribution(pool, &version_ids)
|
||||
let missing = get_files_missing_attribution(&**ro_pool, &version_ids)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
enrich_dependency_attributions(&mut versions, ro_pool).await;
|
||||
|
||||
Ok(versions
|
||||
.into_iter()
|
||||
.map(|v| {
|
||||
|
||||
@@ -808,6 +808,18 @@ impl DBVersion {
|
||||
}
|
||||
).await?;
|
||||
|
||||
let dependency_attributions =
|
||||
crate::queue::file_scan::get_dependency_attributions(
|
||||
&mut exec,
|
||||
&version_ids
|
||||
.iter()
|
||||
.copied()
|
||||
.map(DBVersionId)
|
||||
.collect_vec(),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
let res = sqlx::query!(
|
||||
r#"
|
||||
SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,
|
||||
@@ -832,6 +844,19 @@ impl DBVersion {
|
||||
let hashes = hashes.remove(&version_id).map(|x|x.1).unwrap_or_default();
|
||||
let version_fields = version_fields.remove(&version_id).map(|x|x.1).unwrap_or_default();
|
||||
let dependencies = dependencies.remove(&version_id).map(|x|x.1).unwrap_or_default();
|
||||
let dependencies = dependencies
|
||||
.into_iter()
|
||||
.map(|mut dependency| {
|
||||
if let Some(attr) = dependency_attributions.get(&dependency.id)
|
||||
&& (attr.attribution.flame_project.is_some()
|
||||
|| attr.attribution.resolution.is_some())
|
||||
{
|
||||
dependency.attribution = Some(attr.attribution.clone());
|
||||
}
|
||||
|
||||
dependency
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
let loader_fields = loader_fields.iter()
|
||||
.filter(|x| loader_loader_field_ids.contains(&x.id))
|
||||
@@ -1030,6 +1055,22 @@ impl DBVersion {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn clear_cache_ids(
|
||||
version_ids: &[DBVersionId],
|
||||
redis: &RedisPool,
|
||||
) -> Result<(), DatabaseError> {
|
||||
let mut redis = redis.connect().await?;
|
||||
|
||||
redis
|
||||
.delete_many(
|
||||
version_ids
|
||||
.iter()
|
||||
.map(|id| (VERSIONS_NAMESPACE, Some(id.0.to_string()))),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
|
||||
@@ -25,6 +25,8 @@ pub mod util;
|
||||
|
||||
const DEFAULT_EXPIRY: i64 = 60 * 60 * 12; // 12 hours
|
||||
const ACTUAL_EXPIRY: i64 = 60 * 30; // 30 minutes
|
||||
const VERSION_DEFAULT_EXPIRY: i64 = 60 * 60 * 48; // 48 hours
|
||||
const VERSION_ACTUAL_EXPIRY: i64 = 60 * 60 * 24; // 24 hours
|
||||
|
||||
// Bound how many commands we send in a single Redis pipeline. The multiplexed
|
||||
// connection's BytesMut write buffer keeps its peak capacity for the life of
|
||||
@@ -39,6 +41,15 @@ const MGET_CHUNK_SIZE: usize = 32;
|
||||
// BytesMut peak capacity that builds up under steady load.
|
||||
const REDIS_MAX_CONN_AGE: Duration = Duration::from_secs(120);
|
||||
|
||||
fn cache_expiries(namespace: &str) -> (i64, i64) {
|
||||
match namespace {
|
||||
"versions" | "versions_files" => {
|
||||
(VERSION_DEFAULT_EXPIRY, VERSION_ACTUAL_EXPIRY)
|
||||
}
|
||||
_ => (DEFAULT_EXPIRY, ACTUAL_EXPIRY),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RedisPool {
|
||||
pub url: String,
|
||||
@@ -372,6 +383,7 @@ impl RedisPool {
|
||||
.instrument(info_span!("get cached values"))
|
||||
};
|
||||
|
||||
let (default_expiry, actual_expiry) = cache_expiries(namespace);
|
||||
let current_time = Utc::now();
|
||||
let mut expired_values = HashMap::new();
|
||||
|
||||
@@ -379,7 +391,7 @@ impl RedisPool {
|
||||
let mut cached_values = cached_values_raw
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| {
|
||||
if Utc.timestamp_opt(val.iat + ACTUAL_EXPIRY, 0).unwrap()
|
||||
if Utc.timestamp_opt(val.iat + actual_expiry, 0).unwrap()
|
||||
< current_time
|
||||
{
|
||||
expired_values.insert(val.key.to_string(), val);
|
||||
@@ -481,7 +493,7 @@ impl RedisPool {
|
||||
self.meta_namespace
|
||||
),
|
||||
serde_json::to_string(&value)?,
|
||||
DEFAULT_EXPIRY as u64,
|
||||
default_expiry as u64,
|
||||
);
|
||||
pipe_cmds += 1;
|
||||
|
||||
@@ -501,7 +513,7 @@ impl RedisPool {
|
||||
self.meta_namespace, actual_slug
|
||||
),
|
||||
key.to_string(),
|
||||
DEFAULT_EXPIRY as u64,
|
||||
default_expiry as u64,
|
||||
);
|
||||
pipe_cmds += 1;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@ pub async fn scan_all_files(
|
||||
file_id,
|
||||
&overrides,
|
||||
&resolved,
|
||||
redis,
|
||||
&mut txn,
|
||||
)
|
||||
.await
|
||||
@@ -164,7 +165,7 @@ pub async fn scan_file(
|
||||
})?;
|
||||
|
||||
persist_attribution_results(
|
||||
project_id, file_id, &overrides, &resolved, txn,
|
||||
project_id, file_id, &overrides, &resolved, redis, txn,
|
||||
)
|
||||
.await
|
||||
.wrap_err_with(|| {
|
||||
@@ -303,6 +304,7 @@ async fn persist_attribution_results(
|
||||
file_id: DBFileId,
|
||||
overrides: &[OverrideFile],
|
||||
resolved: &HashMap<String, OverrideResolution>,
|
||||
redis: &RedisPool,
|
||||
txn: &mut PgTransaction<'_>,
|
||||
) -> Result<()> {
|
||||
let all_sha1s: Vec<Vec<u8>> = overrides
|
||||
@@ -552,6 +554,22 @@ async fn persist_attribution_results(
|
||||
.wrap_err("inserting override file sources")?;
|
||||
}
|
||||
|
||||
let version_id = sqlx::query_scalar!(
|
||||
r#"
|
||||
select version_id as "version_id: DBVersionId"
|
||||
from files
|
||||
where id = $1
|
||||
"#,
|
||||
file_id as DBFileId,
|
||||
)
|
||||
.fetch_one(&mut *txn)
|
||||
.await
|
||||
.wrap_err("fetching scanned file version")?;
|
||||
|
||||
DBVersion::clear_cache_ids(&[version_id], redis)
|
||||
.await
|
||||
.wrap_err("clearing version cache after attribution scan")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,8 @@ impl AutomatedModerationQueue {
|
||||
for project in projects {
|
||||
async {
|
||||
let project =
|
||||
database::DBProject::get_id((project).into(), &pool, &redis).await?;
|
||||
database::DBProject::get_id((project).into(), &*ro_pool, &redis)
|
||||
.await?;
|
||||
|
||||
if let Some(project) = project {
|
||||
let res = async {
|
||||
@@ -282,7 +283,7 @@ impl AutomatedModerationQueue {
|
||||
}
|
||||
|
||||
let versions =
|
||||
database::DBVersion::get_many(&project.versions, &pool, &redis)
|
||||
database::DBVersion::get_many(&project.versions, &*ro_pool, &redis)
|
||||
.await?
|
||||
.into_iter()
|
||||
// we only support modpacks at this time
|
||||
@@ -374,7 +375,7 @@ impl AutomatedModerationQueue {
|
||||
let versions_data = filter_visible_versions(
|
||||
database::models::DBVersion::get_many(
|
||||
&version_ids,
|
||||
&pool,
|
||||
&*ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await?,
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::PgPool;
|
||||
use crate::database::models::{
|
||||
DBOrganization, DBTeamMember,
|
||||
DBOrganization, DBTeamMember, DBVersion,
|
||||
ids::{
|
||||
DBAttributionGroupId, DBProjectId, DBVersionId,
|
||||
generate_attribution_group_id,
|
||||
@@ -143,31 +143,59 @@ async fn scan(
|
||||
project_ids.sort_unstable_by_key(|id| id.0);
|
||||
project_ids.dedup_by_key(|id| id.0);
|
||||
|
||||
for project_id in project_ids {
|
||||
for project_id in &project_ids {
|
||||
ensure_can_upload_versions_to_project(
|
||||
pool.as_ref(),
|
||||
project_id,
|
||||
*project_id,
|
||||
&user,
|
||||
"you do not have permission to upload versions to this project",
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let project_ids = project_ids.iter().map(|id| id.0).collect::<Vec<_>>();
|
||||
let mut transaction = pool
|
||||
.begin()
|
||||
.await
|
||||
.wrap_internal_err("failed to begin attribution scan transaction")?;
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
delete from attributions_exemptions
|
||||
where version_id = any($1) or project_id = any($2)
|
||||
"#,
|
||||
&version_ids,
|
||||
&project_ids,
|
||||
)
|
||||
.execute(&mut transaction)
|
||||
.await
|
||||
.wrap_internal_err("failed to remove attribution scan exemptions")?;
|
||||
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
insert into file_scans (file_id)
|
||||
select f.id
|
||||
from files f
|
||||
inner join attribution_enforced_versions aev on aev.id = f.version_id
|
||||
where f.version_id = any($1)
|
||||
on conflict (file_id) do nothing
|
||||
"#,
|
||||
&version_ids,
|
||||
)
|
||||
.execute(pool.as_ref())
|
||||
.execute(&mut transaction)
|
||||
.await
|
||||
.wrap_internal_err("failed to queue version files for attribution scan")?;
|
||||
|
||||
transaction
|
||||
.commit()
|
||||
.await
|
||||
.wrap_internal_err("failed to commit attribution scan transaction")?;
|
||||
|
||||
let version_ids =
|
||||
version_ids.into_iter().map(DBVersionId).collect::<Vec<_>>();
|
||||
DBVersion::clear_cache_ids(&version_ids, redis.as_ref())
|
||||
.await
|
||||
.wrap_internal_err("failed to clear version cache")?;
|
||||
|
||||
Ok(web::Json(ScanResponse {
|
||||
queued_files: result.rows_affected(),
|
||||
}))
|
||||
@@ -491,6 +519,9 @@ async fn update_group(
|
||||
return Err(ApiError::NotFound);
|
||||
}
|
||||
|
||||
clear_group_version_cache(pool.as_ref(), redis.as_ref(), &[group_id])
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -640,6 +671,14 @@ async fn assign(
|
||||
.await
|
||||
.wrap_internal_err("failed to clean up empty attribution groups")?;
|
||||
|
||||
clear_project_sha1_version_cache(
|
||||
pool.as_ref(),
|
||||
redis.as_ref(),
|
||||
project_id,
|
||||
&sha1_bytes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -745,6 +784,72 @@ async fn split(
|
||||
.await
|
||||
.wrap_internal_err("failed to clean up empty attribution groups")?;
|
||||
|
||||
clear_project_sha1_version_cache(
|
||||
pool.as_ref(),
|
||||
redis.as_ref(),
|
||||
project_id,
|
||||
&sha1_bytes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_group_version_cache(
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
group_ids: &[i64],
|
||||
) -> Result<(), ApiError> {
|
||||
let version_ids = sqlx::query_scalar!(
|
||||
r#"
|
||||
select distinct f.version_id as "version_id: DBVersionId"
|
||||
from project_attribution_files paf
|
||||
inner join project_attribution_groups pag on pag.id = paf.group_id
|
||||
inner join override_file_sources ofs on ofs.sha1 = paf.sha1
|
||||
inner join files f on f.id = ofs.file_id
|
||||
inner join versions v on v.id = f.version_id
|
||||
where paf.group_id = any($1)
|
||||
and pag.project_id = v.mod_id
|
||||
"#,
|
||||
group_ids,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.wrap_internal_err("failed to fetch attribution group versions")?;
|
||||
|
||||
DBVersion::clear_cache_ids(&version_ids, redis)
|
||||
.await
|
||||
.wrap_internal_err("failed to clear version attribution cache")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_project_sha1_version_cache(
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
project_id: DBProjectId,
|
||||
sha1: &[u8],
|
||||
) -> Result<(), ApiError> {
|
||||
let version_ids = sqlx::query_scalar!(
|
||||
r#"
|
||||
select distinct f.version_id as "version_id: DBVersionId"
|
||||
from override_file_sources ofs
|
||||
inner join files f on f.id = ofs.file_id
|
||||
inner join versions v on v.id = f.version_id
|
||||
where ofs.sha1 = $1
|
||||
and v.mod_id = $2
|
||||
"#,
|
||||
sha1,
|
||||
project_id as DBProjectId,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.wrap_internal_err("failed to fetch attribution file versions")?;
|
||||
|
||||
DBVersion::clear_cache_ids(&version_ids, redis)
|
||||
.await
|
||||
.wrap_internal_err("failed to clear version attribution cache")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ pub async fn forge_updates(
|
||||
|
||||
let versions = database::models::DBVersion::get_many(
|
||||
&project.versions,
|
||||
&**pool,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1307,7 +1307,8 @@ pub async fn dependency_list_internal(
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result = db_models::DBProject::get(&string, &**pool, &redis).await?;
|
||||
let result =
|
||||
db_models::DBProject::get(&string, &***ro_pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -1329,7 +1330,7 @@ pub async fn dependency_list_internal(
|
||||
|
||||
let dependencies = database::DBProject::get_dependencies(
|
||||
project.inner.id,
|
||||
&**pool,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
@@ -1355,11 +1356,19 @@ pub async fn dependency_list_internal(
|
||||
.unique()
|
||||
.collect::<Vec<db_models::DBVersionId>>();
|
||||
let (projects_result, versions_result) = futures::future::try_join(
|
||||
database::DBProject::get_many_ids(&project_ids, &**pool, &redis),
|
||||
database::DBProject::get_many_ids(
|
||||
&project_ids,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
),
|
||||
async {
|
||||
database::DBVersion::get_many(&dep_version_ids, &**pool, &redis)
|
||||
.await
|
||||
.wrap_internal_err("failed to fetch dependency versions")
|
||||
database::DBVersion::get_many(
|
||||
&dep_version_ids,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await
|
||||
.wrap_internal_err("failed to fetch dependency versions")
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -251,7 +251,7 @@ pub async fn get_versions_from_hashes(
|
||||
.await?,
|
||||
&user_option,
|
||||
&pool,
|
||||
&pool,
|
||||
pool.as_ref(),
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -2,8 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use super::ApiError;
|
||||
use crate::auth::checks::{
|
||||
enrich_dependency_attributions, filter_visible_versions,
|
||||
is_visible_project, is_visible_version,
|
||||
filter_visible_versions, is_visible_project, is_visible_version,
|
||||
};
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database;
|
||||
@@ -81,7 +80,7 @@ pub async fn version_project_get_helper(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let result =
|
||||
database::models::DBProject::get(&id.0, &**pool, &redis).await?;
|
||||
database::models::DBProject::get(&id.0, &***ro_pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -103,7 +102,7 @@ pub async fn version_project_get_helper(
|
||||
|
||||
let versions = database::models::DBVersion::get_many(
|
||||
&project.versions,
|
||||
&**pool,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
@@ -114,20 +113,16 @@ pub async fn version_project_get_helper(
|
||||
|| x.inner.version_number == id.1
|
||||
});
|
||||
|
||||
if let Some(mut version) = version
|
||||
if let Some(version) = version
|
||||
&& is_visible_version(&version.inner, &user_option, &pool, &redis)
|
||||
.await?
|
||||
{
|
||||
let version_id = version.inner.id;
|
||||
enrich_dependency_attributions(
|
||||
std::slice::from_mut(&mut version),
|
||||
&ro_pool,
|
||||
)
|
||||
.await;
|
||||
let mut v = models::projects::Version::from(version);
|
||||
let missing = get_files_missing_attribution(&**pool, &[version_id])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let missing =
|
||||
get_files_missing_attribution(&***ro_pool, &[version_id])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
v.files_missing_attribution = missing
|
||||
.get(&version_id)
|
||||
.map(|entries| {
|
||||
@@ -180,9 +175,12 @@ pub async fn versions_get(
|
||||
.into_iter()
|
||||
.map(|x| x.into())
|
||||
.collect::<Vec<database::models::DBVersionId>>();
|
||||
let versions_data =
|
||||
database::models::DBVersion::get_many(&version_ids, &**pool, &redis)
|
||||
.await?;
|
||||
let versions_data = database::models::DBVersion::get_many(
|
||||
&version_ids,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -234,7 +232,8 @@ pub async fn version_get_helper(
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<web::Json<models::projects::Version>, ApiError> {
|
||||
let version_data =
|
||||
database::models::DBVersion::get(id.into(), &**pool, &redis).await?;
|
||||
database::models::DBVersion::get(id.into(), &***ro_pool, &redis)
|
||||
.await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -247,17 +246,12 @@ pub async fn version_get_helper(
|
||||
.map(|x| x.1)
|
||||
.ok();
|
||||
|
||||
if let Some(mut data) = version_data
|
||||
if let Some(data) = version_data
|
||||
&& is_visible_version(&data.inner, &user_option, &pool, &redis).await?
|
||||
{
|
||||
let version_id = data.inner.id;
|
||||
enrich_dependency_attributions(
|
||||
std::slice::from_mut(&mut data),
|
||||
&ro_pool,
|
||||
)
|
||||
.await;
|
||||
let mut version = models::projects::Version::from(data);
|
||||
let missing = get_files_missing_attribution(&**pool, &[version_id])
|
||||
let missing = get_files_missing_attribution(&***ro_pool, &[version_id])
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
version.files_missing_attribution = missing
|
||||
@@ -839,7 +833,7 @@ pub async fn version_list_internal(
|
||||
let string = info.into_inner().0;
|
||||
|
||||
let result =
|
||||
database::models::DBProject::get(&string, &**pool, &redis).await?;
|
||||
database::models::DBProject::get(&string, &***ro_pool, &redis).await?;
|
||||
|
||||
let user_option = get_user_from_headers(
|
||||
&req,
|
||||
@@ -868,7 +862,7 @@ pub async fn version_list_internal(
|
||||
});
|
||||
let mut versions = database::models::DBVersion::get_many(
|
||||
&project.versions,
|
||||
&**pool,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
)
|
||||
.await?
|
||||
@@ -926,11 +920,14 @@ pub async fn version_list_internal(
|
||||
// TODO: This is a bandaid fix for detecting auto-featured versions.
|
||||
// In the future, not all versions will have 'game_versions' fields, so this will need to be changed.
|
||||
let (loaders, game_versions) = futures::future::try_join(
|
||||
database::models::loader_fields::Loader::list(&**pool, &redis),
|
||||
database::models::loader_fields::Loader::list(
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
),
|
||||
database::models::legacy_loader_fields::MinecraftGameVersion::list(
|
||||
None,
|
||||
Some(true),
|
||||
&**pool,
|
||||
&***ro_pool,
|
||||
&redis,
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user