diff --git a/.sqlx/query-029f912ebf303c28443b81c0272c76da716781e22a20fead1147fa3ef43ed932.json b/.sqlx/query-029f912ebf303c28443b81c0272c76da716781e22a20fead1147fa3ef43ed932.json deleted file mode 100644 index 0410228..0000000 --- a/.sqlx/query-029f912ebf303c28443b81c0272c76da716781e22a20fead1147fa3ef43ed932.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT md.mod_version_id FROM mod_downloads md\n INNER JOIN mod_versions mv ON md.mod_version_id = mv.id\n INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id\n WHERE mv.mod_id = $2 AND mvs.status = 'accepted' AND ip = $1 LIMIT 1;\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "mod_version_id", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Inet", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "029f912ebf303c28443b81c0272c76da716781e22a20fead1147fa3ef43ed932" -} diff --git a/.sqlx/query-0965f8bc73b6d9b46df0f47dad338cd5dcf17d5ca5427349273029c1d1c47a23.json b/.sqlx/query-0965f8bc73b6d9b46df0f47dad338cd5dcf17d5ca5427349273029c1d1c47a23.json new file mode 100644 index 0000000..8e48f4a --- /dev/null +++ b/.sqlx/query-0965f8bc73b6d9b46df0f47dad338cd5dcf17d5ca5427349273029c1d1c47a23.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mod_versions\n SET download_count = download_count + 1\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [] + }, + "hash": "0965f8bc73b6d9b46df0f47dad338cd5dcf17d5ca5427349273029c1d1c47a23" +} diff --git a/.sqlx/query-0e95ef8707ac61465d5567b516ac7b84401b72d97d904b05d5b59ad000e4efeb.json b/.sqlx/query-0e95ef8707ac61465d5567b516ac7b84401b72d97d904b05d5b59ad000e4efeb.json deleted file mode 100644 index 80432e8..0000000 --- a/.sqlx/query-0e95ef8707ac61465d5567b516ac7b84401b72d97d904b05d5b59ad000e4efeb.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT mod_id FROM mod_versions WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "mod_id", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false - ] - }, - "hash": "0e95ef8707ac61465d5567b516ac7b84401b72d97d904b05d5b59ad000e4efeb" -} diff --git a/.sqlx/query-0eed3c807b61381f067354bf1f573587610f5e01e75b3d205401623af7a94d65.json b/.sqlx/query-0eed3c807b61381f067354bf1f573587610f5e01e75b3d205401623af7a94d65.json new file mode 100644 index 0000000..2cf06ce --- /dev/null +++ b/.sqlx/query-0eed3c807b61381f067354bf1f573587610f5e01e75b3d205401623af7a94d65.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT ip FROM mod_downloads md\n INNER JOIN mod_versions mv ON md.mod_version_id = mv.id\n WHERE mv.mod_id = $1\n AND md.ip = $2\n LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "ip", + "type_info": "Inet" + } + ], + "parameters": { + "Left": [ + "Text", + "Inet" + ] + }, + "nullable": [ + false + ] + }, + "hash": "0eed3c807b61381f067354bf1f573587610f5e01e75b3d205401623af7a94d65" +} diff --git a/.sqlx/query-1196f230c31488941d2c89c9e2250dbd5c872220058f067fe889ead45df5aea7.json b/.sqlx/query-1196f230c31488941d2c89c9e2250dbd5c872220058f067fe889ead45df5aea7.json deleted file mode 100644 index ada2180..0000000 --- a/.sqlx/query-1196f230c31488941d2c89c9e2250dbd5c872220058f067fe889ead45df5aea7.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mods m SET download_count = (\n SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md\n INNER JOIN mod_versions mv ON md.mod_version_id = mv.id\n INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id\n WHERE mv.mod_id = m.id AND mvs.status = 'accepted'\n ), last_download_cache_refresh = now()\n WHERE m.id IN (\n SELECT DISTINCT mv.mod_id FROM mod_versions mv \n INNER JOIN mod_version_statuses mvs ON mv.status_id = mvs.id\n WHERE mvs.status = 'accepted'\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "1196f230c31488941d2c89c9e2250dbd5c872220058f067fe889ead45df5aea7" -} diff --git a/.sqlx/query-215bf2a1b4a67f3bca7b6472280ce3874a9b5dae1723a2db893563b985919852.json b/.sqlx/query-215bf2a1b4a67f3bca7b6472280ce3874a9b5dae1723a2db893563b985919852.json new file mode 100644 index 0000000..39fed2e --- /dev/null +++ b/.sqlx/query-215bf2a1b4a67f3bca7b6472280ce3874a9b5dae1723a2db893563b985919852.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO mod_downloads (mod_version_id, ip)\n VALUES ($1, $2)\n ON CONFLICT DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Inet" + ] + }, + "nullable": [] + }, + "hash": "215bf2a1b4a67f3bca7b6472280ce3874a9b5dae1723a2db893563b985919852" +} diff --git a/.sqlx/query-278148dfb6364e07848f65cbc6b5821592cbd1414cbe78b8054316e3c5bf06b2.json b/.sqlx/query-278148dfb6364e07848f65cbc6b5821592cbd1414cbe78b8054316e3c5bf06b2.json deleted file mode 100644 index 669be83..0000000 --- a/.sqlx/query-278148dfb6364e07848f65cbc6b5821592cbd1414cbe78b8054316e3c5bf06b2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT mod_version_id FROM mod_downloads md\n WHERE mod_version_id = $1 LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "mod_version_id", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false - ] - }, - "hash": "278148dfb6364e07848f65cbc6b5821592cbd1414cbe78b8054316e3c5bf06b2" -} diff --git a/.sqlx/query-8bfeec7431745b3ccee5f23eafc2c9522a1892499f37978e1e32bb4b28718725.json b/.sqlx/query-8bfeec7431745b3ccee5f23eafc2c9522a1892499f37978e1e32bb4b28718725.json deleted file mode 100644 index 506aed2..0000000 --- a/.sqlx/query-8bfeec7431745b3ccee5f23eafc2c9522a1892499f37978e1e32bb4b28718725.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mods m\n SET download_count = download_count + 1\n WHERE m.id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "8bfeec7431745b3ccee5f23eafc2c9522a1892499f37978e1e32bb4b28718725" -} diff --git a/.sqlx/query-97fc4685af3c2880263ced471f2081f7313eca11e2b08fe383f521f939b6fa02.json b/.sqlx/query-97fc4685af3c2880263ced471f2081f7313eca11e2b08fe383f521f939b6fa02.json new file mode 100644 index 0000000..b8aa352 --- /dev/null +++ b/.sqlx/query-97fc4685af3c2880263ced471f2081f7313eca11e2b08fe383f521f939b6fa02.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM mod_downloads md\n WHERE md.time_downloaded <= $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Timestamptz" + ] + }, + "nullable": [] + }, + "hash": "97fc4685af3c2880263ced471f2081f7313eca11e2b08fe383f521f939b6fa02" +} diff --git a/.sqlx/query-a5f311305436e69a55ecf06cb8c04f604162f91d9b2644df3b81e02ff1576220.json b/.sqlx/query-a5f311305436e69a55ecf06cb8c04f604162f91d9b2644df3b81e02ff1576220.json new file mode 100644 index 0000000..9311e02 --- /dev/null +++ b/.sqlx/query-a5f311305436e69a55ecf06cb8c04f604162f91d9b2644df3b81e02ff1576220.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mods\n SET download_count = download_count + 1\n WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "a5f311305436e69a55ecf06cb8c04f604162f91d9b2644df3b81e02ff1576220" +} diff --git a/.sqlx/query-aa57dd8a81be512862dcf376b533f83feb978cae69a12150bda352ac2d1270a8.json b/.sqlx/query-aa57dd8a81be512862dcf376b533f83feb978cae69a12150bda352ac2d1270a8.json deleted file mode 100644 index 4be1976..0000000 --- a/.sqlx/query-aa57dd8a81be512862dcf376b533f83feb978cae69a12150bda352ac2d1270a8.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mod_versions mv\n SET download_count = mv.download_count + 1\n FROM mod_version_statuses mvs\n WHERE mv.id = $1 AND mvs.mod_version_id = mv.id AND mvs.status = 'accepted'", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [] - }, - "hash": "aa57dd8a81be512862dcf376b533f83feb978cae69a12150bda352ac2d1270a8" -} diff --git a/.sqlx/query-b9ab774a4b98bcf5082b49c4acc3366e921681a47bb1fb66177fb2c8164ceb2b.json b/.sqlx/query-b9ab774a4b98bcf5082b49c4acc3366e921681a47bb1fb66177fb2c8164ceb2b.json deleted file mode 100644 index ff1576d..0000000 --- a/.sqlx/query-b9ab774a4b98bcf5082b49c4acc3366e921681a47bb1fb66177fb2c8164ceb2b.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mod_versions mv \n SET download_count = (\n SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md\n WHERE md.mod_version_id = mv.id\n ), last_download_cache_refresh = now()\n FROM mod_version_statuses mvs\n WHERE mv.id = $1 AND mvs.mod_version_id = mv.id AND mvs.status = 'accepted'", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [] - }, - "hash": "b9ab774a4b98bcf5082b49c4acc3366e921681a47bb1fb66177fb2c8164ceb2b" -} diff --git a/.sqlx/query-be4320e0e13d6e4dd6c927f632620f8e1ac8e9e5665608861a947b27176a3d35.json b/.sqlx/query-be4320e0e13d6e4dd6c927f632620f8e1ac8e9e5665608861a947b27176a3d35.json deleted file mode 100644 index ba788e7..0000000 --- a/.sqlx/query-be4320e0e13d6e4dd6c927f632620f8e1ac8e9e5665608861a947b27176a3d35.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT mod_version_id FROM mod_downloads md\n WHERE ip = $1 AND mod_version_id = $2 FOR UPDATE\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "mod_version_id", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Inet", - "Int4" - ] - }, - "nullable": [ - false - ] - }, - "hash": "be4320e0e13d6e4dd6c927f632620f8e1ac8e9e5665608861a947b27176a3d35" -} diff --git a/.sqlx/query-dcd87f6f6e96c21cb535315ae20765f0418b792b0788b1b9fd389439dcf027a5.json b/.sqlx/query-dcd87f6f6e96c21cb535315ae20765f0418b792b0788b1b9fd389439dcf027a5.json deleted file mode 100644 index 401e42a..0000000 --- a/.sqlx/query-dcd87f6f6e96c21cb535315ae20765f0418b792b0788b1b9fd389439dcf027a5.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO mod_downloads (ip, mod_version_id)\n VALUES ($1, $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Inet", - "Int4" - ] - }, - "nullable": [] - }, - "hash": "dcd87f6f6e96c21cb535315ae20765f0418b792b0788b1b9fd389439dcf027a5" -} diff --git a/.sqlx/query-e14b31d4bf44c2e6a92d7d6509ed516878122602078d4e84a12380ab8f2c5b02.json b/.sqlx/query-e14b31d4bf44c2e6a92d7d6509ed516878122602078d4e84a12380ab8f2c5b02.json deleted file mode 100644 index 45bc7bb..0000000 --- a/.sqlx/query-e14b31d4bf44c2e6a92d7d6509ed516878122602078d4e84a12380ab8f2c5b02.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mod_versions mv SET download_count = (\n SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md\n WHERE md.mod_version_id = mv.id\n ), last_download_cache_refresh = now()\n FROM mod_version_statuses mvs\n WHERE mv.status_id = mvs.id AND mvs.status = 'accepted'", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "e14b31d4bf44c2e6a92d7d6509ed516878122602078d4e84a12380ab8f2c5b02" -} diff --git a/.sqlx/query-e9ec9aba4cfdd593f5dc8d33b03c28cad813a8ee4a31920ffbef1a395d5a14f5.json b/.sqlx/query-e9ec9aba4cfdd593f5dc8d33b03c28cad813a8ee4a31920ffbef1a395d5a14f5.json deleted file mode 100644 index 5766fa5..0000000 --- a/.sqlx/query-e9ec9aba4cfdd593f5dc8d33b03c28cad813a8ee4a31920ffbef1a395d5a14f5.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE mods m SET download_count = (\n SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md\n INNER JOIN mod_versions mv ON md.mod_version_id = mv.id\n INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id\n WHERE mv.mod_id = m.id AND mvs.status = 'accepted'\n ), last_download_cache_refresh = now()\n WHERE m.id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "e9ec9aba4cfdd593f5dc8d33b03c28cad813a8ee4a31920ffbef1a395d5a14f5" -} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 0d99bc6..ea85690 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,5 @@ use crate::{jobs, AppData}; -use anyhow::anyhow; use clap::{Parser, Subcommand}; -use sqlx::Acquire; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] @@ -19,8 +17,8 @@ pub enum Commands { #[derive(Debug, Subcommand)] pub enum JobCommand { - /// Caches download counts for all mods currently stored - CacheDownloads, + /// Cleans up mod_downloads from more than 30 days ago + CleanupDownloads, /// Runs migrations Migrate, } @@ -31,20 +29,15 @@ pub async fn maybe_cli(data: &AppData) -> anyhow::Result { if let Some(c) = cli.command { return match c { Commands::Job(job) => match job { - JobCommand::CacheDownloads => { + JobCommand::Migrate => { let mut conn = data.db.acquire().await?; - let mut transaction = conn.begin().await?; - - jobs::download_cache::start(&mut transaction) - .await - .map_err(|e| anyhow!("Failed to update download cache {}", e))?; + jobs::migrate::migrate(&mut conn).await?; - transaction.commit().await?; Ok(true) - } - JobCommand::Migrate => { + }, + JobCommand::CleanupDownloads => { let mut conn = data.db.acquire().await?; - jobs::migrate::migrate(&mut conn).await?; + jobs::cleanup_downloads::cleanup_downloads(&mut *conn).await?; Ok(true) } diff --git a/src/database/repository/mod.rs b/src/database/repository/mod.rs index 93b03d6..dd3d574 100644 --- a/src/database/repository/mod.rs +++ b/src/database/repository/mod.rs @@ -1,3 +1,5 @@ pub mod mod_tags; pub mod mods; -pub mod developers; \ No newline at end of file +pub mod developers; +pub mod mod_downloads; +pub mod mod_versions; \ No newline at end of file diff --git a/src/database/repository/mod_downloads.rs b/src/database/repository/mod_downloads.rs new file mode 100644 index 0000000..d79c9d9 --- /dev/null +++ b/src/database/repository/mod_downloads.rs @@ -0,0 +1,70 @@ +use crate::types::api::ApiError; +use chrono::{Days, Utc}; +use sqlx::types::ipnetwork::IpNetwork; +use sqlx::PgConnection; + +pub async fn create( + ip: IpNetwork, + mod_version_id: i32, + conn: &mut PgConnection, +) -> Result { + let result = sqlx::query!( + "INSERT INTO mod_downloads (mod_version_id, ip) + VALUES ($1, $2) + ON CONFLICT DO NOTHING", + mod_version_id, + ip + ) + .execute(&mut *conn) + .await + .map_err(|e| { + log::error!( + "Failed to insert new download for mod_version id {}: {}", + mod_version_id, + e + ); + return ApiError::DbError; + })?; + + Ok(result.rows_affected() > 0) +} + +pub async fn has_downloaded_mod( + ip: IpNetwork, + mod_id: &str, + conn: &mut PgConnection, +) -> Result { + Ok(sqlx::query!( + "SELECT ip FROM mod_downloads md + INNER JOIN mod_versions mv ON md.mod_version_id = mv.id + WHERE mv.mod_id = $1 + AND md.ip = $2 + LIMIT 1", + mod_id, + ip + ) + .fetch_optional(&mut *conn) + .await + .map_err(|e| { + log::error!("Failed to check if mod has been downloaded: {}", e); + ApiError::DbError + })? + .is_some()) +} + +pub async fn cleanup(conn: &mut PgConnection) -> Result<(), ApiError> { + let date = Utc::now().checked_sub_days(Days::new(30)).unwrap(); + sqlx::query!( + "DELETE FROM mod_downloads md + WHERE md.time_downloaded <= $1", + date + ) + .execute(&mut *conn) + .await + .map_err(|e| { + log::error!("Failed to cleanup downloads: {}", e); + ApiError::DbError + })?; + + Ok(()) +} diff --git a/src/database/repository/mod_versions.rs b/src/database/repository/mod_versions.rs new file mode 100644 index 0000000..1d3be63 --- /dev/null +++ b/src/database/repository/mod_versions.rs @@ -0,0 +1,23 @@ +use crate::types::api::ApiError; +use sqlx::PgConnection; + +pub async fn increment_downloads(id: i32, conn: &mut PgConnection) -> Result<(), ApiError> { + sqlx::query!( + "UPDATE mod_versions + SET download_count = download_count + 1 + WHERE id = $1", + id + ) + .execute(&mut *conn) + .await + .map_err(|e| { + log::error!( + "Failed to increment downloads for mod_version {}: {}", + id, + e + ); + ApiError::DbError + })?; + + Ok(()) +} diff --git a/src/database/repository/mods.rs b/src/database/repository/mods.rs index 51fa58b..1fc488d 100644 --- a/src/database/repository/mods.rs +++ b/src/database/repository/mods.rs @@ -55,3 +55,20 @@ pub async fn get_logo(id: &str, conn: &mut PgConnection) -> Result Result<(), ApiError> { + sqlx::query!( + "UPDATE mods + SET download_count = download_count + 1 + WHERE id = $1", + id + ) + .execute(&mut *conn) + .await + .map_err(|e| { + log::error!("Failed to increment downloads for mod {}: {}", id, e); + ApiError::DbError + })?; + + Ok(()) +} diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 2fd1a70..2611212 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -4,7 +4,7 @@ use actix_web::{dev::ConnectionInfo, get, post, put, web, HttpResponse, Responde use serde::Deserialize; use sqlx::{types::ipnetwork::IpNetwork, Acquire}; -use crate::database::repository::developers; +use crate::database::repository::{developers, mod_downloads, mod_versions, mods}; use crate::events::mod_created::{ NewModAcceptedEvent, NewModVersionAcceptedEvent, NewModVersionVerification, }; @@ -16,7 +16,6 @@ use crate::{ mod_json::{split_version_and_compare, ModJson}, models::{ developer::Developer, - download, mod_entity::{download_geode_file, Mod}, mod_gd_version::{GDVersionEnum, VerPlatform}, mod_version::{self, ModVersion}, @@ -194,7 +193,7 @@ pub async fn download_version( }; let url = mod_version.download_link; - if data.disable_downloads { + if data.disable_downloads || mod_version.status != ModVersionStatusEnum::Accepted { // whatever return Ok(HttpResponse::Found() .append_header(("Location", url)) @@ -207,44 +206,22 @@ pub async fn download_version( }; let net: IpNetwork = ip.parse().or(Err(ApiError::InternalError))?; - if let Ok((downloaded_version, downloaded_mod)) = - download::create_download(net, mod_version.id, &mod_version.mod_id, &mut pool).await - { - let name = mod_version.mod_id.clone(); - let version = mod_version.version.clone(); - - // only accepted mods can have their download counts incremented - // we'll just fix this once they're updated anyways - - if (downloaded_version || downloaded_mod) - && mod_version.status == ModVersionStatusEnum::Accepted - { - tokio::spawn(async move { - if downloaded_version { - // we must nest more - if let Err(e) = ModVersion::increment_downloads(mod_version.id, &mut pool).await - { - log::error!( - "Failed to increment downloads for mod version {}. Error: {}", - version, - e - ); - } - } - - if downloaded_mod { - if let Err(e) = Mod::increment_downloads(&mod_version.mod_id, &mut pool).await { - log::error!( - "Failed to increment downloads for mod {}. Error: {}", - name, - e - ); - } - } - }); + let mut tx = pool.begin().await.or(Err(ApiError::TransactionError))?; + + let downloaded_mod_previously = + mod_downloads::has_downloaded_mod(net, &mod_version.mod_id, &mut tx).await?; + let inserted = mod_downloads::create(net, mod_version.id, &mut tx).await?; + + if inserted { + mod_versions::increment_downloads(mod_version.id, &mut tx).await?; + + if !downloaded_mod_previously { + mods::increment_downloads(&mod_version.mod_id, &mut tx).await?; } } + let _ = tx.commit().await; + Ok(HttpResponse::Found() .append_header(("Location", url)) .finish()) diff --git a/src/jobs/cleanup_downloads.rs b/src/jobs/cleanup_downloads.rs new file mode 100644 index 0000000..211ec20 --- /dev/null +++ b/src/jobs/cleanup_downloads.rs @@ -0,0 +1,9 @@ +use sqlx::PgConnection; +use crate::database::repository::mod_downloads; +use crate::types::api::ApiError; + +pub async fn cleanup_downloads(conn: &mut PgConnection) -> Result<(), ApiError> { + mod_downloads::cleanup(conn).await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/jobs/download_cache.rs b/src/jobs/download_cache.rs deleted file mode 100644 index 8bf93a8..0000000 --- a/src/jobs/download_cache.rs +++ /dev/null @@ -1,41 +0,0 @@ -use sqlx::PgConnection; - -pub async fn start(pool: &mut PgConnection) -> Result<(), String> { - // update mod_versions counts - if let Err(e) = sqlx::query!( - "UPDATE mod_versions mv SET download_count = ( - SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md - WHERE md.mod_version_id = mv.id - ), last_download_cache_refresh = now() - FROM mod_version_statuses mvs - WHERE mv.status_id = mvs.id AND mvs.status = 'accepted'" - ) - .execute(&mut *pool) - .await - { - log::error!("{}", e); - return Err("Error updating mod version download count".to_string()); - } - - if let Err(e) = sqlx::query!( - "UPDATE mods m SET download_count = ( - SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md - INNER JOIN mod_versions mv ON md.mod_version_id = mv.id - INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id - WHERE mv.mod_id = m.id AND mvs.status = 'accepted' - ), last_download_cache_refresh = now() - WHERE m.id IN ( - SELECT DISTINCT mv.mod_id FROM mod_versions mv - INNER JOIN mod_version_statuses mvs ON mv.status_id = mvs.id - WHERE mvs.status = 'accepted' - )" - ) - .execute(&mut *pool) - .await - { - log::error!("{}", e); - return Err("Error updating mod download count".to_string()); - } - - Ok(()) -} diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index 5d0f73e..3d2d1c9 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -1,2 +1,2 @@ -pub mod download_cache; +pub mod cleanup_downloads; pub mod migrate; diff --git a/src/types/models/download.rs b/src/types/models/download.rs deleted file mode 100644 index 751d585..0000000 --- a/src/types/models/download.rs +++ /dev/null @@ -1,104 +0,0 @@ -use sqlx::{types::ipnetwork::IpNetwork, PgConnection, Acquire}; - -use crate::types::api::ApiError; - -pub async fn downloaded_version(mod_version_id: i32, pool: &mut PgConnection) -> Result { - match sqlx::query!( - r#" - SELECT mod_version_id FROM mod_downloads md - WHERE mod_version_id = $1 LIMIT 1 - "#, - mod_version_id - ) - .fetch_optional(&mut *pool) - .await - { - Ok(e) => Ok(e.is_some()), - Err(e) => { - log::error!("{}", e); - Err(ApiError::InternalError) - } - } -} - -pub async fn create_download( - ip: IpNetwork, - mod_version_id: i32, - mod_id: &str, - pool: &mut PgConnection, -) -> Result<(bool, bool), ApiError> { - // hold it in a transaction, so we don't get duplicate downloads or something - let mut tx = pool.begin().await.or(Err(ApiError::TransactionError))?; - - let existing = match sqlx::query!( - r#" - SELECT mod_version_id FROM mod_downloads md - WHERE ip = $1 AND mod_version_id = $2 FOR UPDATE - "#, - ip, - mod_version_id - ) - .fetch_optional(&mut *tx) - .await - { - Ok(e) => e, - Err(e) => { - log::error!("{}", e); - return Err(ApiError::InternalError); - } - }; - - if existing.is_some() { - // we don't really care about a read transaction failing - tx.commit().await.or(Err(ApiError::TransactionError))?; - - return Ok((false, false)); - } - - // determine if the user has ever downloaded this mod - // this is probably wasteful tbh - - let existing_mod = match sqlx::query!( - r#" - SELECT md.mod_version_id FROM mod_downloads md - INNER JOIN mod_versions mv ON md.mod_version_id = mv.id - INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id - WHERE mv.mod_id = $2 AND mvs.status = 'accepted' AND ip = $1 LIMIT 1; - "#, - ip, - mod_id - ) - .fetch_optional(&mut *tx) - .await - { - Ok(e) => e, - Err(e) => { - log::error!("{}", e); - return Err(ApiError::InternalError); - } - }; - - let downloaded_mod = existing_mod.is_some(); - - match sqlx::query!( - r#" - INSERT INTO mod_downloads (ip, mod_version_id) - VALUES ($1, $2) - "#, - ip, - mod_version_id - ) - .execute(&mut *tx) - .await - { - Ok(_) => { - tx.commit().await.or(Err(ApiError::TransactionError))?; - - Ok((true, !downloaded_mod)) - }, - Err(e) => { - log::error!("{}", e); - Err(ApiError::InternalError) - } - } -} diff --git a/src/types/models/mod.rs b/src/types/models/mod.rs index 142755b..c4f9bfb 100644 --- a/src/types/models/mod.rs +++ b/src/types/models/mod.rs @@ -1,6 +1,5 @@ pub mod dependency; pub mod developer; -pub mod download; pub mod github_login_attempt; pub mod incompatibility; pub mod mod_entity; diff --git a/src/types/models/mod_entity.rs b/src/types/models/mod_entity.rs index 50adb5d..0d2359a 100644 --- a/src/types/models/mod_entity.rs +++ b/src/types/models/mod_entity.rs @@ -1062,52 +1062,6 @@ impl Mod { Ok(()) } - pub async fn increment_downloads( - mod_id: &str, - pool: &mut PgConnection, - ) -> Result<(), ApiError> { - sqlx::query!( - "UPDATE mods m - SET download_count = download_count + 1 - WHERE m.id = $1", - mod_id - ) - .execute(&mut *pool) - .await - .map_err(|e| { - log::error!( - "Failed to increment download count for mod {}: {}", - mod_id, - e - ); - ApiError::DbError - })?; - Ok(()) - } - - pub async fn calculate_cached_downloads( - mod_id: &str, - pool: &mut PgConnection, - ) -> Result<(), ApiError> { - sqlx::query!( - "UPDATE mods m SET download_count = ( - SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md - INNER JOIN mod_versions mv ON md.mod_version_id = mv.id - INNER JOIN mod_version_statuses mvs ON mvs.mod_version_id = mv.id - WHERE mv.mod_id = m.id AND mvs.status = 'accepted' - ), last_download_cache_refresh = now() - WHERE m.id = $1", - mod_id - ) - .execute(&mut *pool) - .await - .map_err(|e| { - log::error!("Failed to recalculate downloads for mod {}: {}", mod_id, e); - ApiError::DbError - })?; - Ok(()) - } - pub async fn assign_owner( mod_id: &str, dev_id: i32, diff --git a/src/types/models/mod_version.rs b/src/types/models/mod_version.rs index 7f8fde0..374c0ba 100644 --- a/src/types/models/mod_version.rs +++ b/src/types/models/mod_version.rs @@ -11,7 +11,7 @@ use sqlx::{ use crate::types::{ api::{create_download_link, ApiError, PaginatedData}, mod_json::ModJson, - models::{download, mod_entity::Mod}, + models::mod_entity::Mod, }; use super::{ @@ -760,50 +760,6 @@ impl ModVersion { Ok(version) } - pub async fn increment_downloads( - mod_version_id: i32, - pool: &mut PgConnection, - ) -> Result<(), ApiError> { - // we aren't recalculating, so don't reset the timer - if let Err(e) = sqlx::query!( - "UPDATE mod_versions mv - SET download_count = mv.download_count + 1 - FROM mod_version_statuses mvs - WHERE mv.id = $1 AND mvs.mod_version_id = mv.id AND mvs.status = 'accepted'", - mod_version_id - ) - .execute(&mut *pool) - .await - { - log::error!("{}", e); - return Err(ApiError::DbError); - } - Ok(()) - } - - pub async fn calculate_cached_downloads( - mod_version_id: i32, - pool: &mut PgConnection, - ) -> Result<(), ApiError> { - if let Err(e) = sqlx::query!( - "UPDATE mod_versions mv - SET download_count = ( - SELECT COUNT(DISTINCT md.ip) FROM mod_downloads md - WHERE md.mod_version_id = mv.id - ), last_download_cache_refresh = now() - FROM mod_version_statuses mvs - WHERE mv.id = $1 AND mvs.mod_version_id = mv.id AND mvs.status = 'accepted'", - mod_version_id - ) - .execute(&mut *pool) - .await - { - log::error!("{}", e); - return Err(ApiError::DbError); - } - Ok(()) - } - pub async fn update_version( id: i32, new_status: ModVersionStatusEnum, @@ -863,27 +819,6 @@ impl ModVersion { return Err(ApiError::DbError); } - if download::downloaded_version(id, pool).await? { - // performing this operation will change the mod's download count, so recalculate it - - // should probably spawn this, but we do a download in the transaction which is probably - // a little worse. idk - - let info = match sqlx::query!("SELECT mod_id FROM mod_versions WHERE id = $1", id) - .fetch_one(&mut *pool) - .await - { - Err(e) => { - log::error!("{}", e); - return Err(ApiError::DbError); - } - Ok(r) => r, - }; - - ModVersion::calculate_cached_downloads(id, pool).await?; - Mod::calculate_cached_downloads(&info.mod_id, pool).await?; - } - if current_status.status == ModVersionStatusEnum::Pending && new_status == ModVersionStatusEnum::Accepted {