From 7a383b98b9d1187f1d055da780b27619ada33fd9 Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:13:58 +0100 Subject: [PATCH] Remove legacy analytics routes --- ...7070d2abac466048c870b2f23497848cf55b3.json | 37 - ...32beedc4b3b1c6de8365b7b523b9d917977b2.json | 37 - .../src/routes/v3/analytics_get/mod.rs | 2 - .../src/routes/v3/analytics_get/old.rs | 766 ------------------ apps/labrinth/src/routes/v3/mod.rs | 6 - apps/labrinth/src/test/api_v3/project.rs | 72 -- 6 files changed, 920 deletions(-) delete mode 100644 apps/labrinth/.sqlx/query-33181ac6fe503d4ca6b396d6ddd7070d2abac466048c870b2f23497848cf55b3.json delete mode 100644 apps/labrinth/.sqlx/query-41da353ec0bfb3209f095e1e4f932beedc4b3b1c6de8365b7b523b9d917977b2.json delete mode 100644 apps/labrinth/src/routes/v3/analytics_get/old.rs diff --git a/apps/labrinth/.sqlx/query-33181ac6fe503d4ca6b396d6ddd7070d2abac466048c870b2f23497848cf55b3.json b/apps/labrinth/.sqlx/query-33181ac6fe503d4ca6b396d6ddd7070d2abac466048c870b2f23497848cf55b3.json deleted file mode 100644 index b30c98b1bb..0000000000 --- a/apps/labrinth/.sqlx/query-33181ac6fe503d4ca6b396d6ddd7070d2abac466048c870b2f23497848cf55b3.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT mod_id, SUM(amount) amount_sum, DATE_BIN($4::interval, created, TIMESTAMP '2001-01-01') AS interval_start\n FROM payouts_values\n WHERE user_id = $1 AND created >= $2 AND created < $3\n GROUP by mod_id, interval_start ORDER BY interval_start\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "mod_id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "amount_sum", - "type_info": "Numeric" - }, - { - "ordinal": 2, - "name": "interval_start", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Int8", - "Timestamptz", - "Timestamptz", - "Interval" - ] - }, - "nullable": [ - true, - null, - null - ] - }, - "hash": "33181ac6fe503d4ca6b396d6ddd7070d2abac466048c870b2f23497848cf55b3" -} diff --git a/apps/labrinth/.sqlx/query-41da353ec0bfb3209f095e1e4f932beedc4b3b1c6de8365b7b523b9d917977b2.json b/apps/labrinth/.sqlx/query-41da353ec0bfb3209f095e1e4f932beedc4b3b1c6de8365b7b523b9d917977b2.json deleted file mode 100644 index e4c019979a..0000000000 --- a/apps/labrinth/.sqlx/query-41da353ec0bfb3209f095e1e4f932beedc4b3b1c6de8365b7b523b9d917977b2.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT mod_id, SUM(amount) amount_sum, DATE_BIN($4::interval, created, TIMESTAMP '2001-01-01') AS interval_start\n FROM payouts_values\n WHERE mod_id = ANY($1) AND created >= $2 AND created < $3\n GROUP by mod_id, interval_start ORDER BY interval_start\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "mod_id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "amount_sum", - "type_info": "Numeric" - }, - { - "ordinal": 2, - "name": "interval_start", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Int8Array", - "Timestamptz", - "Timestamptz", - "Interval" - ] - }, - "nullable": [ - true, - null, - null - ] - }, - "hash": "41da353ec0bfb3209f095e1e4f932beedc4b3b1c6de8365b7b523b9d917977b2" -} diff --git a/apps/labrinth/src/routes/v3/analytics_get/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/mod.rs index 7c6b60d2ca..c74e3acaae 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/mod.rs @@ -14,7 +14,6 @@ use xredis::RedisPool; pub mod facets; mod metrics; -pub mod old; use std::{ collections::{HashMap, HashSet}, @@ -61,7 +60,6 @@ pub use metrics::*; pub fn config(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(fetch_analytics); cfg.configure(facets::config); - cfg.configure(old::config); } // request diff --git a/apps/labrinth/src/routes/v3/analytics_get/old.rs b/apps/labrinth/src/routes/v3/analytics_get/old.rs deleted file mode 100644 index a164921783..0000000000 --- a/apps/labrinth/src/routes/v3/analytics_get/old.rs +++ /dev/null @@ -1,766 +0,0 @@ -//! TODO: this module should be removed; it is superseded by `analytics_get` - -use crate::util::error::ApiContext as _; -use crate::util::error::Context as _; - -use super::ApiError; -use crate::database; -use crate::database::PgPool; -use crate::models::teams::ProjectPermissions; -use crate::{ - auth::get_user_from_headers, - database::models::user_item, - models::{ids::ProjectId, pats::Scopes}, - queue::session::AuthQueue, -}; -use actix_web::{HttpRequest, HttpResponse, get, web}; -use ariadne::ids::base62_impl::to_base62; -use chrono::{DateTime, Duration, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::postgres::types::PgInterval; -use std::collections::HashMap; -use std::convert::TryInto; -use std::num::NonZeroU32; -use xredis::RedisPool; - -pub fn config(cfg: &mut actix_web::web::ServiceConfig) { - cfg.service(playtimes_get) - .service(views_get) - .service(downloads_get) - .service(revenue_get) - .service(countries_downloads_get) - .service(countries_views_get); -} - -/// The json data to be passed to fetch analytic data. -/// -/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out. -/// start_date and end_date are optional, and default to two weeks ago, and the maximum date respectively. -/// resolution_minutes is optional. This refers to the window by which we are looking (every day, every minute, etc) and defaults to 1440 (1 day) -#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)] -pub struct GetData { - // only one of project_ids or version_ids should be used - // if neither are provided, all projects the user has access to will be used - pub project_ids: Option, - - pub start_date: Option>, // defaults to 2 weeks ago - pub end_date: Option>, // defaults to now - - #[schema(value_type = Option, minimum = 1)] - pub resolution_minutes: Option, // defaults to 1 day. Ignored in routes that do not aggregate over a resolution (eg: /countries) -} - -/// Get playtime data. -#[utoipa::path( - context_path = "/analytics", - tag = "analytics", - params( - ("project_ids" = Option, Query), - ("start_date" = Option, Query), - ("end_date" = Option, Query), - ("resolution_minutes" = Option, Query) - ), - responses((status = OK, body = HashMap>)), -)] -#[get("/playtime")] -pub async fn playtimes_get( - req: HttpRequest, - clickhouse: web::Data, - data: web::Query, - session_queue: web::Data, - pool: web::Data, - redis: web::Data, -) -> Result { - let user = get_user_from_headers( - &req, - &**pool, - &redis, - &session_queue, - Scopes::ANALYTICS, - ) - .await - .map(|x| x.1) - .wrap_auth_err("authenticating API request")?; - - let project_ids = data - .project_ids - .as_ref() - .map(|ids| serde_json::from_str::>(ids)) - .transpose() - .wrap_request_err("deserializing JSON data")?; - - let start_date = data.start_date.unwrap_or(Utc::now() - Duration::weeks(2)); - let end_date = data.end_date.unwrap_or(Utc::now()); - let resolution_minutes = data - .resolution_minutes - .map_or(60 * 24, |minutes| minutes.get()); - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - // - If no project_ids or version_ids are provided, we default to all projects the user has access to - let project_ids = - filter_allowed_ids(project_ids, user, &pool, &redis, None) - .await - .wrap_api_err("filtering authorized playtime project IDs")?; - - // Get the views - let playtimes = crate::clickhouse::fetch_playtimes( - project_ids.unwrap_or_default(), - start_date, - end_date, - resolution_minutes, - clickhouse.into_inner(), - ) - .await - .wrap_api_err("fetching project playtime from ClickHouse")?; - - let mut hm = HashMap::new(); - for playtime in playtimes { - let id_string = to_base62(playtime.id); - if !hm.contains_key(&id_string) { - hm.insert(id_string.clone(), HashMap::new()); - } - if let Some(hm) = hm.get_mut(&id_string) { - hm.insert(playtime.time, playtime.total); - } - } - - Ok(HttpResponse::Ok().json(hm)) -} - -/// Get view data. -/// -/// Data is returned as a hashmap of project/version ids to a hashmap of days to views -/// eg: -/// { -/// "4N1tEhnO": { -/// "20230824": 1090 -/// } -///} -/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out. -#[utoipa::path( - context_path = "/analytics", - tag = "analytics", - params( - ("project_ids" = Option, Query), - ("start_date" = Option, Query), - ("end_date" = Option, Query), - ("resolution_minutes" = Option, Query) - ), - responses((status = OK, body = HashMap>)), -)] -#[get("/views")] -pub async fn views_get( - req: HttpRequest, - clickhouse: web::Data, - data: web::Query, - session_queue: web::Data, - pool: web::Data, - redis: web::Data, -) -> Result { - let user = get_user_from_headers( - &req, - &**pool, - &redis, - &session_queue, - Scopes::ANALYTICS, - ) - .await - .map(|x| x.1) - .wrap_auth_err("authenticating API request")?; - - let project_ids = data - .project_ids - .as_ref() - .map(|ids| serde_json::from_str::>(ids)) - .transpose() - .wrap_request_err("deserializing JSON data")?; - - let start_date = data.start_date.unwrap_or(Utc::now() - Duration::weeks(2)); - let end_date = data.end_date.unwrap_or(Utc::now()); - let resolution_minutes = data - .resolution_minutes - .map_or(60 * 24, |minutes| minutes.get()); - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - // - If no project_ids or version_ids are provided, we default to all projects the user has access to - let project_ids = - filter_allowed_ids(project_ids, user, &pool, &redis, None) - .await - .wrap_api_err("filtering authorized view project IDs")?; - - // Get the views - let views = crate::clickhouse::fetch_views( - project_ids.unwrap_or_default(), - start_date, - end_date, - resolution_minutes, - clickhouse.into_inner(), - ) - .await - .wrap_api_err("fetching project views from ClickHouse")?; - - let mut hm = HashMap::new(); - for views in views { - let id_string = to_base62(views.id); - if !hm.contains_key(&id_string) { - hm.insert(id_string.clone(), HashMap::new()); - } - if let Some(hm) = hm.get_mut(&id_string) { - hm.insert(views.time, views.total); - } - } - - Ok(HttpResponse::Ok().json(hm)) -} - -/// Get download data. -/// -/// Data is returned as a hashmap of project/version ids to a hashmap of days to downloads -/// eg: -/// { -/// "4N1tEhnO": { -/// "20230824": 32 -/// } -///} -/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out. -#[utoipa::path( - context_path = "/analytics", - tag = "analytics", - params( - ("project_ids" = Option, Query), - ("start_date" = Option, Query), - ("end_date" = Option, Query), - ("resolution_minutes" = Option, Query) - ), - responses((status = OK, body = HashMap>)), -)] -#[get("/downloads")] -pub async fn downloads_get( - req: HttpRequest, - clickhouse: web::Data, - data: web::Query, - session_queue: web::Data, - pool: web::Data, - redis: web::Data, -) -> Result { - let user_option = get_user_from_headers( - &req, - &**pool, - &redis, - &session_queue, - Scopes::ANALYTICS, - ) - .await - .map(|x| x.1) - .wrap_auth_err("authenticating API request")?; - - let project_ids = data - .project_ids - .as_ref() - .map(|ids| serde_json::from_str::>(ids)) - .transpose() - .wrap_request_err("deserializing JSON data")?; - - let start_date = data.start_date.unwrap_or(Utc::now() - Duration::weeks(2)); - let end_date = data.end_date.unwrap_or(Utc::now()); - let resolution_minutes = data - .resolution_minutes - .map_or(60 * 24, |minutes| minutes.get()); - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - // - If no project_ids or version_ids are provided, we default to all projects the user has access to - let project_ids = - filter_allowed_ids(project_ids, user_option, &pool, &redis, None) - .await - .wrap_api_err("filtering authorized download project IDs")?; - - // Get the downloads - let downloads = crate::clickhouse::fetch_downloads( - project_ids.unwrap_or_default(), - start_date, - end_date, - resolution_minutes, - clickhouse.into_inner(), - ) - .await - .wrap_api_err("fetching project downloads from ClickHouse")?; - - let mut hm = HashMap::new(); - for downloads in downloads { - let id_string = to_base62(downloads.id); - if !hm.contains_key(&id_string) { - hm.insert(id_string.clone(), HashMap::new()); - } - if let Some(hm) = hm.get_mut(&id_string) { - hm.insert(downloads.time, downloads.total); - } - } - - Ok(HttpResponse::Ok().json(hm)) -} - -/// Get payout data. -/// -/// Data is returned as a hashmap of project ids to a hashmap of days to amount earned per day -/// eg: -/// { -/// "4N1tEhnO": { -/// "20230824": 0.001 -/// } -///} -/// ONLY project IDs can be used. Unauthorized projects will be filtered out. -#[utoipa::path( - context_path = "/analytics", - tag = "analytics", - params( - ("project_ids" = Option, Query), - ("start_date" = Option, Query), - ("end_date" = Option, Query), - ("resolution_minutes" = Option, Query) - ), - responses((status = OK, body = HashMap>)), -)] -#[get("/revenue")] -pub async fn revenue_get( - req: HttpRequest, - data: web::Query, - session_queue: web::Data, - pool: web::Data, - redis: web::Data, -) -> Result { - let user = get_user_from_headers( - &req, - &**pool, - &redis, - &session_queue, - Scopes::PAYOUTS_READ, - ) - .await - .map(|x| x.1) - .wrap_auth_err("authenticating API request")?; - - let project_ids = data - .project_ids - .as_ref() - .map(|ids| serde_json::from_str::>(ids)) - .transpose() - .wrap_request_err("deserializing JSON data")?; - - let start_date = data.start_date.unwrap_or(Utc::now() - Duration::weeks(2)); - let end_date = data.end_date.unwrap_or(Utc::now()); - let resolution_minutes = data - .resolution_minutes - .map_or(60 * 24, |minutes| minutes.get()); - - // Round up/down to nearest duration as we are using pgadmin, does not have rounding in the fetch command - // Round start_date down to nearest resolution - let diff = start_date.timestamp() % (resolution_minutes as i64 * 60); - let start_date = start_date - Duration::seconds(diff); - - // Round end_date up to nearest resolution - let diff = end_date.timestamp() % (resolution_minutes as i64 * 60); - let end_date = - end_date + Duration::seconds((resolution_minutes as i64 * 60) - diff); - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - // - If no project_ids or version_ids are provided, we default to all projects the user has access to - let project_ids = filter_allowed_ids( - project_ids, - user.clone(), - &pool, - &redis, - Some(true), - ) - .await - .wrap_api_err("filtering authorized revenue project IDs")?; - - let duration: PgInterval = Duration::minutes(resolution_minutes as i64) - .try_into() - .map_err(|err: Box| { - eyre::eyre!("{err}") - }) - .wrap_request_err("invalid `resolution_minutes`")?; - // Get the revenue data - let project_ids = project_ids.unwrap_or_default(); - - struct PayoutValue { - mod_id: Option, - amount_sum: Option, - interval_start: Option>, - } - - let payouts_values = if project_ids.is_empty() { - sqlx::query!( - " - SELECT mod_id, SUM(amount) amount_sum, DATE_BIN($4::interval, created, TIMESTAMP '2001-01-01') AS interval_start - FROM payouts_values - WHERE user_id = $1 AND created >= $2 AND created < $3 - GROUP by mod_id, interval_start ORDER BY interval_start - ", - user.id.0 as i64, - start_date, - end_date, - duration, - ) - .fetch_all(&**pool) - .await.wrap_internal_err("fetching payouts values from database")?.into_iter().map(|x| PayoutValue { - mod_id: x.mod_id, - amount_sum: x.amount_sum, - interval_start: x.interval_start, - }).collect::>() - } else { - sqlx::query!( - " - SELECT mod_id, SUM(amount) amount_sum, DATE_BIN($4::interval, created, TIMESTAMP '2001-01-01') AS interval_start - FROM payouts_values - WHERE mod_id = ANY($1) AND created >= $2 AND created < $3 - GROUP by mod_id, interval_start ORDER BY interval_start - ", - &project_ids.iter().map(|x| x.0 as i64).collect::>(), - start_date, - end_date, - duration, - ) - .fetch_all(&**pool) - .await.wrap_internal_err("querying database for `revenue_get`")?.into_iter().map(|x| PayoutValue { - mod_id: x.mod_id, - amount_sum: x.amount_sum, - interval_start: x.interval_start, - }).collect::>() - }; - - let mut hm: HashMap<_, _> = project_ids - .into_iter() - .map(|x| (x.to_string(), HashMap::new())) - .collect::>(); - for value in payouts_values { - if let Some(mod_id) = value.mod_id - && let Some(amount) = value.amount_sum - && let Some(interval_start) = value.interval_start - { - let id_string = to_base62(mod_id as u64); - if !hm.contains_key(&id_string) { - hm.insert(id_string.clone(), HashMap::new()); - } - if let Some(hm) = hm.get_mut(&id_string) { - hm.insert(interval_start.timestamp(), amount); - } - } - } - - Ok(HttpResponse::Ok().json(hm)) -} - -/// Get download country data. -/// -/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to downloads. -/// Unknown countries are labeled "". -/// This is usable to see significant performing countries per project -/// eg: -/// { -/// "4N1tEhnO": { -/// "CAN": 22 -/// } -///} -/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out. -/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch -#[utoipa::path( - context_path = "/analytics", - tag = "analytics", - params( - ("project_ids" = Option, Query), - ("start_date" = Option, Query), - ("end_date" = Option, Query), - ("resolution_minutes" = Option, Query) - ), - responses((status = OK, body = HashMap>)), -)] -#[get("/countries/downloads")] -pub async fn countries_downloads_get( - req: HttpRequest, - clickhouse: web::Data, - data: web::Query, - session_queue: web::Data, - pool: web::Data, - redis: web::Data, -) -> Result { - let user = get_user_from_headers( - &req, - &**pool, - &redis, - &session_queue, - Scopes::ANALYTICS, - ) - .await - .map(|x| x.1) - .wrap_auth_err("authenticating API request")?; - - let project_ids = data - .project_ids - .as_ref() - .map(|ids| serde_json::from_str::>(ids)) - .transpose() - .wrap_request_err("deserializing JSON data")?; - - let start_date = data.start_date.unwrap_or(Utc::now() - Duration::weeks(2)); - let end_date = data.end_date.unwrap_or(Utc::now()); - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - // - If no project_ids or version_ids are provided, we default to all projects the user has access to - let project_ids = - filter_allowed_ids(project_ids, user, &pool, &redis, None) - .await - .wrap_api_err( - "filtering authorized download-country project IDs", - )?; - - // Get the countries - let countries = crate::clickhouse::fetch_countries_downloads( - project_ids.unwrap_or_default(), - start_date, - end_date, - clickhouse.into_inner(), - ) - .await - .wrap_api_err("fetching download countries from ClickHouse")?; - - let mut hm = HashMap::new(); - for views in countries { - let id_string = to_base62(views.id); - if !hm.contains_key(&id_string) { - hm.insert(id_string.clone(), HashMap::new()); - } - if let Some(hm) = hm.get_mut(&id_string) { - hm.insert(views.country, views.total); - } - } - - let hm: HashMap> = hm - .into_iter() - .map(|(key, value)| (key, condense_countries(value))) - .collect(); - - Ok(HttpResponse::Ok().json(hm)) -} - -/// Get view country data. -/// -/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to views. -/// Unknown countries are labeled "". -/// This is usable to see significant performing countries per project -/// eg: -/// { -/// "4N1tEhnO": { -/// "CAN": 56165 -/// } -///} -/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out. -/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch -#[utoipa::path( - context_path = "/analytics", - tag = "analytics", - params( - ("project_ids" = Option, Query), - ("start_date" = Option, Query), - ("end_date" = Option, Query), - ("resolution_minutes" = Option, Query) - ), - responses((status = OK, body = HashMap>)), -)] -#[get("/countries/views")] -pub async fn countries_views_get( - req: HttpRequest, - clickhouse: web::Data, - data: web::Query, - session_queue: web::Data, - pool: web::Data, - redis: web::Data, -) -> Result { - let user = get_user_from_headers( - &req, - &**pool, - &redis, - &session_queue, - Scopes::ANALYTICS, - ) - .await - .map(|x| x.1) - .wrap_auth_err("authenticating API request")?; - - let project_ids = data - .project_ids - .as_ref() - .map(|ids| serde_json::from_str::>(ids)) - .transpose() - .wrap_request_err("deserializing JSON data")?; - - let start_date = data.start_date.unwrap_or(Utc::now() - Duration::weeks(2)); - let end_date = data.end_date.unwrap_or(Utc::now()); - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - // - If no project_ids or version_ids are provided, we default to all projects the user has access to - let project_ids = - filter_allowed_ids(project_ids, user, &pool, &redis, None) - .await - .wrap_api_err("filtering authorized view-country project IDs")?; - - // Get the countries - let countries = crate::clickhouse::fetch_countries_views( - project_ids.unwrap_or_default(), - start_date, - end_date, - clickhouse.into_inner(), - ) - .await - .wrap_api_err("fetching view countries from ClickHouse")?; - - let mut hm = HashMap::new(); - for views in countries { - let id_string = to_base62(views.id); - if !hm.contains_key(&id_string) { - hm.insert(id_string.clone(), HashMap::new()); - } - if let Some(hm) = hm.get_mut(&id_string) { - hm.insert(views.country, views.total); - } - } - - let hm: HashMap> = hm - .into_iter() - .map(|(key, value)| (key, condense_countries(value))) - .collect(); - - Ok(HttpResponse::Ok().json(hm)) -} - -fn condense_countries(countries: HashMap) -> HashMap { - // Every country under '15' (view or downloads) should be condensed into 'XX' - let mut hm = HashMap::new(); - for (mut country, count) in countries { - if count < 50 { - country = "XX".to_string(); - } - if !hm.contains_key(&country) { - hm.insert(country.to_string(), 0); - } - if let Some(hm) = hm.get_mut(&country) { - *hm += count; - } - } - hm -} - -async fn filter_allowed_ids( - mut project_ids: Option>, - user: crate::models::users::User, - pool: &web::Data, - redis: &RedisPool, - remove_defaults: Option, -) -> Result>, ApiError> { - // If no project_ids or version_ids are provided, we default to all projects the user has *public* access to - if project_ids.is_none() && !remove_defaults.unwrap_or(false) { - project_ids = Some( - user_item::DBUser::get_projects(user.id.into(), &***pool, redis) - .await - .wrap_internal_err("deleting user from database")? - .into_iter() - .map(|x| ProjectId::from(x).to_string()) - .collect(), - ); - } - - // Convert String list to list of ProjectIds or VersionIds - // - Filter out unauthorized projects/versions - let project_ids = if let Some(project_strings) = project_ids { - let projects_data = database::models::DBProject::get_many( - &project_strings, - &***pool, - redis, - ) - .await - .wrap_api_err("fetching analytics projects")?; - - let team_ids = projects_data - .iter() - .map(|x| x.inner.team_id) - .collect::>(); - let team_members = - database::models::DBTeamMember::get_from_team_full_many( - &team_ids, &***pool, redis, - ) - .await - .wrap_internal_err("fetching team members from database")?; - - let organization_ids = projects_data - .iter() - .filter_map(|x| x.inner.organization_id) - .collect::>(); - let organizations = database::models::DBOrganization::get_many_ids( - &organization_ids, - &***pool, - redis, - ) - .await - .wrap_internal_err("fetching organizations from database")?; - - let organization_team_ids = organizations - .iter() - .map(|x| x.team_id) - .collect::>(); - let organization_team_members = - database::models::DBTeamMember::get_from_team_full_many( - &organization_team_ids, - &***pool, - redis, - ) - .await - .wrap_internal_err("fetching team members from database")?; - - let ids = projects_data - .into_iter() - .filter(|project| { - let team_member = team_members.iter().find(|x| { - x.team_id == project.inner.team_id - && x.user_id == user.id.into() - }); - - let organization = project - .inner - .organization_id - .and_then(|oid| organizations.iter().find(|x| x.id == oid)); - - let organization_team_member = - if let Some(organization) = organization { - organization_team_members.iter().find(|x| { - x.team_id == organization.team_id - && x.user_id == user.id.into() - }) - } else { - None - }; - - let permissions = ProjectPermissions::get_permissions_by_role( - &user.role, - &team_member.cloned(), - &organization_team_member.cloned(), - ) - .unwrap_or_default(); - - permissions.contains(ProjectPermissions::VIEW_ANALYTICS) - }) - .map(|x| x.inner.id.into()) - .collect::>(); - - Some(ids) - } else { - None - }; - // Only one of project_ids or version_ids will be Some - Ok(project_ids) -} diff --git a/apps/labrinth/src/routes/v3/mod.rs b/apps/labrinth/src/routes/v3/mod.rs index fd2e087039..efbd84e5d5 100644 --- a/apps/labrinth/src/routes/v3/mod.rs +++ b/apps/labrinth/src/routes/v3/mod.rs @@ -86,12 +86,6 @@ pub fn config(cfg: &mut web::ServiceConfig) { analytics_event::analytics_events_get, analytics_get::fetch_analytics, analytics_get::facets::fetch_facets, - analytics_get::old::playtimes_get, - analytics_get::old::views_get, - analytics_get::old::downloads_get, - analytics_get::old::revenue_get, - analytics_get::old::countries_downloads_get, - analytics_get::old::countries_views_get, payouts::post_compliance_form, payouts::calculate_fees, payouts::create_payout, diff --git a/apps/labrinth/src/test/api_v3/project.rs b/apps/labrinth/src/test/api_v3/project.rs index 2899bd0954..d76004015b 100644 --- a/apps/labrinth/src/test/api_v3/project.rs +++ b/apps/labrinth/src/test/api_v3/project.rs @@ -12,8 +12,6 @@ use actix_web::{ }; use async_trait::async_trait; use bytes::Bytes; -use chrono::{DateTime, Utc}; -use rust_decimal::Decimal; use serde_json::json; use crate::test::asserts::assert_status; @@ -565,74 +563,4 @@ impl ApiV3 { assert_status!(&resp, StatusCode::OK); test::read_body_json(resp).await } - - pub async fn get_analytics_revenue( - &self, - id_or_slugs: Vec<&str>, - ids_are_version_ids: bool, - start_date: Option>, - end_date: Option>, - resolution_minutes: Option, - pat: Option<&str>, - ) -> ServiceResponse { - let pv_string = if ids_are_version_ids { - let version_string: String = - serde_json::to_string(&id_or_slugs).unwrap(); - let version_string = urlencoding::encode(&version_string); - format!("version_ids={version_string}") - } else { - let projects_string: String = - serde_json::to_string(&id_or_slugs).unwrap(); - let projects_string = urlencoding::encode(&projects_string); - format!("project_ids={projects_string}") - }; - - let mut extra_args = String::new(); - if let Some(start_date) = start_date { - let start_date = start_date.to_rfc3339(); - // let start_date = serde_json::to_string(&start_date).unwrap(); - let start_date = urlencoding::encode(&start_date); - write!(&mut extra_args, "&start_date={start_date}").unwrap(); - } - if let Some(end_date) = end_date { - let end_date = end_date.to_rfc3339(); - // let end_date = serde_json::to_string(&end_date).unwrap(); - let end_date = urlencoding::encode(&end_date); - write!(&mut extra_args, "&end_date={end_date}").unwrap(); - } - if let Some(resolution_minutes) = resolution_minutes { - write!(&mut extra_args, "&resolution_minutes={resolution_minutes}") - .unwrap(); - } - - let req = test::TestRequest::get() - .uri(&format!("/v3/analytics/revenue?{pv_string}{extra_args}",)) - .append_pat(pat) - .to_request(); - - self.call(req).await - } - - pub async fn get_analytics_revenue_deserialized( - &self, - id_or_slugs: Vec<&str>, - ids_are_version_ids: bool, - start_date: Option>, - end_date: Option>, - resolution_minutes: Option, - pat: Option<&str>, - ) -> HashMap> { - let resp = self - .get_analytics_revenue( - id_or_slugs, - ids_are_version_ids, - start_date, - end_date, - resolution_minutes, - pat, - ) - .await; - assert_status!(&resp, StatusCode::OK); - test::read_body_json(resp).await - } }