mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 18:45:15 +00:00
Add analytics meta for downloading dependent projects (#6318)
* Send dependent mod info to backend * Parse meta from query * condense dependent_on and modpack * Analytics dependents response
This commit is contained in:
@@ -2,7 +2,7 @@ use crate::auth::validate::get_user_record_from_bearer_token;
|
||||
use crate::database::PgPool;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::analytics::{Download, DownloadReason};
|
||||
use crate::models::ids::ProjectId;
|
||||
use crate::models::ids::{ProjectId, VersionId};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::analytics::AnalyticsQueue;
|
||||
use crate::queue::session::AuthQueue;
|
||||
@@ -13,10 +13,12 @@ use crate::util::error::Context;
|
||||
use crate::util::guards::admin_key_guard;
|
||||
use crate::util::tags::valid_download_tags;
|
||||
use actix_web::{HttpRequest, HttpResponse, patch, post, web};
|
||||
use ariadne::ids::base62_impl::parse_base62;
|
||||
use eyre::eyre;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tracing::trace;
|
||||
|
||||
@@ -45,10 +47,86 @@ pub struct DownloadMeta {
|
||||
pub reason: Option<DownloadReason>,
|
||||
pub game_version: Option<String>,
|
||||
pub loader: Option<String>,
|
||||
pub dependent_on: Option<VersionId>,
|
||||
}
|
||||
|
||||
pub const DOWNLOAD_META_HEADER: &str = "modrinth-download-meta";
|
||||
|
||||
fn parse_download_meta_version(
|
||||
version_id: &str,
|
||||
field: &str,
|
||||
) -> Result<VersionId, ApiError> {
|
||||
parse_base62(version_id)
|
||||
.map(VersionId)
|
||||
.wrap_request_err_with(|| {
|
||||
eyre!("invalid `{field}` version id '{version_id}'")
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_download_meta_from_query(
|
||||
url: &url::Url,
|
||||
) -> Result<Option<DownloadMeta>, ApiError> {
|
||||
let mut meta = DownloadMeta {
|
||||
reason: None,
|
||||
game_version: None,
|
||||
loader: None,
|
||||
dependent_on: None,
|
||||
};
|
||||
|
||||
for (key, value) in url.query_pairs() {
|
||||
match key.as_ref() {
|
||||
"mr_download_reason" => {
|
||||
meta.reason =
|
||||
Some(DownloadReason::from_str(&value).map_err(|_| {
|
||||
ApiError::Request(eyre!(
|
||||
"invalid download reason specified"
|
||||
))
|
||||
})?);
|
||||
}
|
||||
"mr_game_version" => {
|
||||
meta.game_version = Some(value.into_owned());
|
||||
}
|
||||
"mr_loader" => {
|
||||
meta.loader = Some(value.into_owned());
|
||||
}
|
||||
"mr_dependent_on" => {
|
||||
meta.dependent_on =
|
||||
Some(parse_download_meta_version(&value, "dependent_on")?);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((meta.reason.is_some()
|
||||
|| meta.game_version.is_some()
|
||||
|| meta.loader.is_some()
|
||||
|| meta.dependent_on.is_some())
|
||||
.then_some(meta))
|
||||
}
|
||||
|
||||
async fn resolve_download_attribution_version(
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
version_id: Option<VersionId>,
|
||||
field: &str,
|
||||
) -> Result<u64, ApiError> {
|
||||
let Some(version_id) = version_id else {
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
let version_id =
|
||||
crate::database::models::ids::DBVersionId::from(version_id);
|
||||
|
||||
crate::database::models::DBVersion::get(version_id, pool, redis)
|
||||
.await
|
||||
.wrap_internal_err("failed to fetch download attribution version")?
|
||||
.ok_or_else(|| {
|
||||
ApiError::Request(eyre!("invalid `{field}` version specified"))
|
||||
})?;
|
||||
|
||||
Ok(version_id.0 as u64)
|
||||
}
|
||||
|
||||
// This is an internal route, cannot be used without key
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
@@ -89,10 +167,9 @@ pub async fn count_download(
|
||||
let project_id: crate::database::models::ids::DBProjectId =
|
||||
download_body.project_id.into();
|
||||
|
||||
let id_option =
|
||||
ariadne::ids::base62_impl::parse_base62(&download_body.version_name)
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
let id_option = parse_base62(&download_body.version_name)
|
||||
.ok()
|
||||
.map(|x| x as i64);
|
||||
|
||||
let (version_id, project_id) = if let Some(version) = sqlx::query!(
|
||||
"
|
||||
@@ -138,7 +215,7 @@ pub async fn count_download(
|
||||
.map(Some)
|
||||
.wrap_request_err("invalid download meta")?
|
||||
} else {
|
||||
None
|
||||
parse_download_meta_from_query(&url)?
|
||||
};
|
||||
|
||||
if let Some(meta) = &meta {
|
||||
@@ -162,6 +239,14 @@ pub async fn count_download(
|
||||
}
|
||||
}
|
||||
|
||||
let dependent_on_version_id = resolve_download_attribution_version(
|
||||
&pool,
|
||||
&redis,
|
||||
meta.as_ref().and_then(|m| m.dependent_on),
|
||||
"dependent_on",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let download = Download {
|
||||
recorded: get_current_tenths_of_ms(),
|
||||
domain: url.host_str().unwrap_or_default().to_string(),
|
||||
@@ -212,6 +297,7 @@ pub async fn count_download(
|
||||
.and_then(|m| m.loader.as_ref())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default(),
|
||||
dependent_on_version_id,
|
||||
};
|
||||
trace!("added download {download:#?}");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user