diff --git a/src/databases/database.rs b/src/databases/database.rs index ea0c41a0..96d7b16f 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::databases::mysql::Mysql; use crate::databases::sqlite::Sqlite; +use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::TorrentListing; @@ -233,6 +234,9 @@ pub trait Database: Sync + Send { /// Update a torrent's description with `torrent_id` and `description`. async fn update_torrent_description(&self, torrent_id: i64, description: &str) -> Result<(), Error>; + /// Update a torrent's category with `torrent_id` and `category_id`. + async fn update_torrent_category(&self, torrent_id: i64, category_id: CategoryId) -> Result<(), Error>; + /// Add a new tag. async fn add_tag(&self, name: &str) -> Result<(), Error>; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 5ae75050..60991edd 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -8,6 +8,7 @@ use sqlx::{query, query_as, Acquire, ConnectOptions, MySqlPool}; use crate::databases::database; use crate::databases::database::{Category, Database, Driver, Sorting, TorrentCompact}; +use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::TorrentListing; @@ -704,6 +705,22 @@ impl Database for Mysql { }) } + async fn update_torrent_category(&self, torrent_id: i64, category_id: CategoryId) -> Result<(), database::Error> { + query("UPDATE torrust_torrents SET category_id = ? WHERE torrent_id = ?") + .bind(category_id) + .bind(torrent_id) + .execute(&self.pool) + .await + .map_err(|_| database::Error::Error) + .and_then(|v| { + if v.rows_affected() > 0 { + Ok(()) + } else { + Err(database::Error::TorrentNotFound) + } + }) + } + async fn add_tag(&self, name: &str) -> Result<(), database::Error> { query("INSERT INTO torrust_torrent_tags (name) VALUES (?)") .bind(name) diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 1837e49e..66c509b0 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -8,6 +8,7 @@ use sqlx::{query, query_as, Acquire, ConnectOptions, SqlitePool}; use crate::databases::database; use crate::databases::database::{Category, Database, Driver, Sorting, TorrentCompact}; +use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::TorrentListing; @@ -694,6 +695,22 @@ impl Database for Sqlite { }) } + async fn update_torrent_category(&self, torrent_id: i64, category_id: CategoryId) -> Result<(), database::Error> { + query("UPDATE torrust_torrents SET category_id = $1 WHERE torrent_id = $2") + .bind(category_id) + .bind(torrent_id) + .execute(&self.pool) + .await + .map_err(|_| database::Error::Error) + .and_then(|v| { + if v.rows_affected() > 0 { + Ok(()) + } else { + Err(database::Error::TorrentNotFound) + } + }) + } + async fn add_tag(&self, name: &str) -> Result<(), database::Error> { query("INSERT INTO torrust_torrent_tags (name) VALUES (?)") .bind(name) diff --git a/src/services/torrent.rs b/src/services/torrent.rs index e04e0889..7a7262ba 100644 --- a/src/services/torrent.rs +++ b/src/services/torrent.rs @@ -8,6 +8,7 @@ use super::user::DbUserRepository; use crate::config::Configuration; use crate::databases::database::{Category, Database, Error, Sorting}; use crate::errors::ServiceError; +use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::{DeletedTorrentResponse, TorrentResponse, TorrentsResponse}; use crate::models::torrent::{AddTorrentRequest, TorrentId, TorrentListing}; @@ -358,6 +359,7 @@ impl Index { info_hash: &InfoHash, title: &Option, description: &Option, + category_id: &Option, tags: &Option>, user_id: &UserId, ) -> Result { @@ -372,7 +374,7 @@ impl Index { } self.torrent_info_repository - .update(&torrent_listing.torrent_id, title, description, tags) + .update(&torrent_listing.torrent_id, title, description, category_id, tags) .await?; let torrent_listing = self @@ -473,6 +475,7 @@ impl DbTorrentInfoRepository { torrent_id: &TorrentId, opt_title: &Option, opt_description: &Option, + opt_category_id: &Option, opt_tags: &Option>, ) -> Result<(), Error> { if let Some(title) = &opt_title { @@ -483,6 +486,10 @@ impl DbTorrentInfoRepository { self.database.update_torrent_description(*torrent_id, description).await?; } + if let Some(category_id) = &opt_category_id { + self.database.update_torrent_category(*torrent_id, *category_id).await?; + } + if let Some(tags) = opt_tags { let mut current_tags: Vec = self .database diff --git a/src/tracker/service.rs b/src/tracker/service.rs index e8b17847..c49c7ac1 100644 --- a/src/tracker/service.rs +++ b/src/tracker/service.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use hyper::StatusCode; use log::error; use serde::{Deserialize, Serialize}; @@ -139,10 +140,23 @@ impl Service { .await .map_err(|_| ServiceError::InternalServerError)?; - if let Ok(torrent_info) = response.json::().await { - Ok(torrent_info) + if response.status() == StatusCode::NOT_FOUND { + return Err(ServiceError::TorrentNotFound); + } + + let body = response.text().await; + + if let Ok(body) = body { + let torrent_info = serde_json::from_str(&body); + + if let Ok(torrent_info) = torrent_info { + Ok(torrent_info) + } else { + error!("Failed to parse torrent info from tracker response"); + Err(ServiceError::InternalServerError) + } } else { - error!("Failed to parse torrent info from tracker response"); + error!("Tracker API response without body"); Err(ServiceError::InternalServerError) } } diff --git a/src/web/api/v1/contexts/torrent/forms.rs b/src/web/api/v1/contexts/torrent/forms.rs index df840c70..fbe9f12c 100644 --- a/src/web/api/v1/contexts/torrent/forms.rs +++ b/src/web/api/v1/contexts/torrent/forms.rs @@ -1,10 +1,12 @@ use serde::Deserialize; +use crate::models::category::CategoryId; use crate::models::torrent_tag::TagId; #[derive(Debug, Deserialize)] pub struct UpdateTorrentInfoForm { pub title: Option, pub description: Option, + pub category: Option, pub tags: Option>, } diff --git a/src/web/api/v1/contexts/torrent/handlers.rs b/src/web/api/v1/contexts/torrent/handlers.rs index a3381d71..b3e197ed 100644 --- a/src/web/api/v1/contexts/torrent/handlers.rs +++ b/src/web/api/v1/contexts/torrent/handlers.rs @@ -156,6 +156,7 @@ pub async fn update_torrent_info_handler( &info_hash, &update_torrent_info_form.title, &update_torrent_info_form.description, + &update_torrent_info_form.category, &update_torrent_info_form.tags, &user_id, ) diff --git a/src/web/api/v1/contexts/torrent/mod.rs b/src/web/api/v1/contexts/torrent/mod.rs index 81c1651d..6041e468 100644 --- a/src/web/api/v1/contexts/torrent/mod.rs +++ b/src/web/api/v1/contexts/torrent/mod.rs @@ -241,6 +241,8 @@ //! ---|---|---|---|--- //! `title` | `Option` | The torrent title | No | `MandelbrotSet` //! `description` | `Option` | The torrent description | No | `MandelbrotSet image` +//! `category` | `Option` | The torrent category ID | No | `1` +//! `tags` | `Option>` | The tag Id list | No | `[1,2,3]` //! //! //! Refer to the [`UpdateTorrentInfoForm`](crate::web::api::v1::contexts::torrent::forms::UpdateTorrentInfoForm) diff --git a/tests/common/contexts/torrent/forms.rs b/tests/common/contexts/torrent/forms.rs index 0c4499f1..c6212611 100644 --- a/tests/common/contexts/torrent/forms.rs +++ b/tests/common/contexts/torrent/forms.rs @@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize}; pub struct UpdateTorrentFrom { pub title: Option, pub description: Option, + pub category: Option, + pub tags: Option>, } use reqwest::multipart::Form; diff --git a/tests/e2e/web/api/v1/contexts/torrent/contract.rs b/tests/e2e/web/api/v1/contexts/torrent/contract.rs index 69ad61a5..41ba09a4 100644 --- a/tests/e2e/web/api/v1/contexts/torrent/contract.rs +++ b/tests/e2e/web/api/v1/contexts/torrent/contract.rs @@ -468,7 +468,7 @@ mod for_authenticated_users { } #[tokio::test] - async fn it_should_allow_non_admin_users_to_update_someone_elses_torrents() { + async fn it_should_not_allow_non_admin_users_to_update_someone_elses_torrents() { let mut env = TestEnv::new(); env.start(api::Version::V1).await; @@ -494,6 +494,8 @@ mod for_authenticated_users { UpdateTorrentFrom { title: Some(new_title.clone()), description: Some(new_description.clone()), + category: None, + tags: None, }, ) .await; @@ -537,6 +539,8 @@ mod for_authenticated_users { UpdateTorrentFrom { title: Some(new_title.clone()), description: Some(new_description.clone()), + category: None, + tags: None, }, ) .await; @@ -611,6 +615,8 @@ mod for_authenticated_users { UpdateTorrentFrom { title: Some(new_title.clone()), description: Some(new_description.clone()), + category: None, + tags: None, }, ) .await;