diff --git a/apps/labrinth/.sqlx/query-b617ed1011341416c1c012c00e716a59873a8204e1b122c7c517a1c4437edfb4.json b/apps/labrinth/.sqlx/query-8d38218e5a0c9297be7c6c77acf40a2339b12ff15f1f9e53a27a1c599a33e43b.json similarity index 61% rename from apps/labrinth/.sqlx/query-b617ed1011341416c1c012c00e716a59873a8204e1b122c7c517a1c4437edfb4.json rename to apps/labrinth/.sqlx/query-8d38218e5a0c9297be7c6c77acf40a2339b12ff15f1f9e53a27a1c599a33e43b.json index eb40f08dec..9489929402 100644 --- a/apps/labrinth/.sqlx/query-b617ed1011341416c1c012c00e716a59873a8204e1b122c7c517a1c4437edfb4.json +++ b/apps/labrinth/.sqlx/query-8d38218e5a0c9297be7c6c77acf40a2339b12ff15f1f9e53a27a1c599a33e43b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n WIDTH_BUCKET(\n EXTRACT(EPOCH FROM created)::bigint,\n EXTRACT(EPOCH FROM $1::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n $3::integer\n ) AS bucket,\n CASE WHEN $5 THEN mod_id ELSE 0 END AS mod_id,\n SUM(amount) amount_sum\n FROM payouts_values\n WHERE\n user_id = $4\n -- only project revenue is counted here\n -- for affiliate code revenue, see `affiliate_code_revenue``\n AND payouts_values.mod_id IS NOT NULL\n AND created BETWEEN $1 AND $2\n GROUP BY bucket, mod_id", + "query": "SELECT\n WIDTH_BUCKET(\n EXTRACT(EPOCH FROM created)::bigint,\n EXTRACT(EPOCH FROM $1::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint,\n $3::integer\n ) AS bucket,\n mod_id,\n SUM(amount) amount_sum\n FROM payouts_values\n WHERE\n -- only project revenue is counted here\n -- for affiliate code revenue, see `affiliate_code_revenue`\n payouts_values.mod_id IS NOT NULL\n AND payouts_values.mod_id = ANY($4)\n AND created BETWEEN $1 AND $2\n GROUP BY bucket, mod_id", "describe": { "columns": [ { @@ -24,15 +24,14 @@ "Timestamptz", "Timestamptz", "Int4", - "Int8", - "Bool" + "Int8Array" ] }, "nullable": [ null, - null, + true, null ] }, - "hash": "b617ed1011341416c1c012c00e716a59873a8204e1b122c7c517a1c4437edfb4" + "hash": "8d38218e5a0c9297be7c6c77acf40a2339b12ff15f1f9e53a27a1c599a33e43b" } diff --git a/apps/labrinth/src/routes/v3/analytics_get.rs b/apps/labrinth/src/routes/v3/analytics_get.rs index 51c9a65005..38a78de6a7 100644 --- a/apps/labrinth/src/routes/v3/analytics_get.rs +++ b/apps/labrinth/src/routes/v3/analytics_get.rs @@ -169,6 +169,8 @@ pub enum ProjectDownloadsField { Domain, /// Modrinth site path which was visited, e.g. `/mod/foo`. SitePath, + /// Whether these downloads were monetized or not. + Monetized, /// What country these downloads came from. /// /// To anonymize the data, the country may be reported as `XX`. @@ -329,6 +331,9 @@ pub struct ProjectDownloads { /// [`ProjectDownloadsField::VersionId`]. #[serde(skip_serializing_if = "Option::is_none")] version_id: Option, + /// [`ProjectDownloadsField::Monetized`]. + #[serde(skip_serializing_if = "Option::is_none")] + monetized: Option, /// [`ProjectDownloadsField::Country`]. #[serde(skip_serializing_if = "Option::is_none")] country: Option, @@ -473,6 +478,7 @@ mod query { pub domain: String, pub site_path: String, pub version_id: DBVersionId, + pub monetized: i8, pub country: String, pub reason: String, pub game_version: String, @@ -485,6 +491,7 @@ mod query { const USE_DOMAIN: &str = "{use_domain: Bool}"; const USE_SITE_PATH: &str = "{use_site_path: Bool}"; const USE_VERSION_ID: &str = "{use_version_id: Bool}"; + const USE_MONETIZED: &str = "{use_monetized: Bool}"; const USE_COUNTRY: &str = "{use_country: Bool}"; const USE_REASON: &str = "{use_reason: Bool}"; const USE_GAME_VERSION: &str = "{use_game_version: Bool}"; @@ -497,6 +504,7 @@ mod query { if({USE_DOMAIN}, domain, '') AS domain, if({USE_SITE_PATH}, site_path, '') AS site_path, if({USE_VERSION_ID}, version_id, 0) AS version_id, + if({USE_MONETIZED}, CAST(user_id != 0 AS Int8), -1) AS monetized, if({USE_COUNTRY}, country, '') AS country, if({USE_REASON}, reason, '') AS reason, if({USE_GAME_VERSION}, game_version, '') AS game_version, @@ -509,7 +517,7 @@ mod query { -- not the possibly-zero one, -- by using `downloads.project_id` instead of `project_id` AND downloads.project_id IN {PROJECT_IDS} - GROUP BY bucket, project_id, domain, site_path, version_id, country, reason, game_version, loader" + GROUP BY bucket, project_id, domain, site_path, version_id, monetized, country, reason, game_version, loader" ) }; @@ -731,6 +739,7 @@ pub async fn fetch_analytics( ("use_domain", uses(F::Domain)), ("use_site_path", uses(F::SitePath)), ("use_version_id", uses(F::VersionId)), + ("use_monetized", uses(F::Monetized)), ("use_country", uses(F::Country)), ("use_reason", uses(F::Reason)), ("use_game_version", uses(F::GameVersion)), @@ -749,6 +758,11 @@ pub async fn fetch_analytics( domain: none_if_empty(row.domain), site_path: none_if_empty(row.site_path), version_id: none_if_zero_version_id(row.version_id), + monetized: match row.monetized { + 0 => Some(false), + 1 => Some(true), + _ => None, + }, country, reason: none_if_empty(row.reason) .and_then(|s| s.parse().ok()), @@ -821,11 +835,14 @@ pub async fn fetch_analytics( .await?; } - if let Some(metrics) = &req.return_metrics.project_revenue { + if req.return_metrics.project_revenue.is_some() { if !scopes.contains(Scopes::PAYOUTS_READ) { return Err(AuthenticationError::InvalidCredentials.into()); } + let project_id_values = + project_ids.iter().map(|id| id.0).collect::>(); + let mut rows = sqlx::query!( "SELECT WIDTH_BUCKET( @@ -834,21 +851,20 @@ pub async fn fetch_analytics( EXTRACT(EPOCH FROM $2::timestamp with time zone AT TIME ZONE 'UTC')::bigint, $3::integer ) AS bucket, - CASE WHEN $5 THEN mod_id ELSE 0 END AS mod_id, + mod_id, SUM(amount) amount_sum FROM payouts_values WHERE - user_id = $4 -- only project revenue is counted here - -- for affiliate code revenue, see `affiliate_code_revenue`` - AND payouts_values.mod_id IS NOT NULL + -- for affiliate code revenue, see `affiliate_code_revenue` + payouts_values.mod_id IS NOT NULL + AND payouts_values.mod_id = ANY($4) AND created BETWEEN $1 AND $2 GROUP BY bucket, mod_id", req.time_range.start, req.time_range.end, num_time_slices as i64, - DBUserId::from(user.id) as DBUserId, - metrics.bucket_by.contains(&ProjectRevenueField::ProjectId), + &project_id_values, ) .fetch(&**pool); while let Some(row) = rows.next().await.transpose()? {