diff --git a/migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql b/migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql new file mode 100644 index 00000000..66e7a7df --- /dev/null +++ b/migrations/mysql/20240304165035_torrust_add_nodes_to_torrent.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_nodes ( + node_id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + torrent_id INTEGER NOT NULL, + node_ip VARCHAR(256) NOT NULL, + node_port INTEGER NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) diff --git a/migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql b/migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql new file mode 100644 index 00000000..4efc0966 --- /dev/null +++ b/migrations/sqlite3/20240304165035_torrust_add_nodes_to_torrent.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS torrust_torrent_nodes ( + node_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + torrent_id INTEGER NOT NULL, + node_ip TEXT NOT NULL, + node_port INTEGER NOT NULL, + FOREIGN KEY(torrent_id) REFERENCES torrust_torrents(torrent_id) ON DELETE CASCADE +) \ No newline at end of file diff --git a/share/default/config/index.development.sqlite3.toml b/share/default/config/index.development.sqlite3.toml index 669979af..5a4461e0 100644 --- a/share/default/config/index.development.sqlite3.toml +++ b/share/default/config/index.development.sqlite3.toml @@ -1,4 +1,4 @@ -log_level = "info" +log_level = "debug" [website] name = "Torrust" diff --git a/src/databases/database.rs b/src/databases/database.rs index 010d208a..2d56e22f 100644 --- a/src/databases/database.rs +++ b/src/databases/database.rs @@ -208,11 +208,14 @@ pub trait Database: Sync + Send { let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + let torrent_nodes = self.get_torrent_nodes_from_id(db_torrent.torrent_id).await?; + Ok(Torrent::from_database( &db_torrent, &torrent_files, torrent_announce_urls, torrent_http_seed_urls, + torrent_nodes, )) } @@ -226,11 +229,14 @@ pub trait Database: Sync + Send { let torrent_http_seed_urls = self.get_torrent_http_seed_urls_from_id(db_torrent.torrent_id).await?; + let torrent_nodes = self.get_torrent_nodes_from_id(db_torrent.torrent_id).await?; + Ok(Torrent::from_database( &db_torrent, &torrent_files, torrent_announce_urls, torrent_http_seed_urls, + torrent_nodes, )) } @@ -274,6 +280,9 @@ pub trait Database: Sync + Send { /// Get all torrent's HTTP seed urls as `Vec>` from `torrent_id`. async fn get_torrent_http_seed_urls_from_id(&self, torrent_id: i64) -> Result, Error>; + /// Get all torrent's nodes as `Vec<(String, i64)>` from `torrent_id`. + async fn get_torrent_nodes_from_id(&self, torrent_id: i64) -> Result, Error>; + /// Get `TorrentListing` from `torrent_id`. async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result; diff --git a/src/databases/mysql.rs b/src/databases/mysql.rs index 87f48e26..07ae8839 100644 --- a/src/databases/mysql.rs +++ b/src/databases/mysql.rs @@ -13,7 +13,9 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; +use crate::models::torrent_file::{ + DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, DbTorrentNode, Torrent, TorrentFile, +}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -606,6 +608,31 @@ impl Database for Mysql { return Err(e); } + // add nodes + + let insert_torrent_nodes_result: Result<(), database::Error> = if let Some(nodes) = &torrent.nodes { + for node in nodes { + let () = query("INSERT INTO torrust_torrent_nodes (torrent_id, node_ip, node_port) VALUES (?, ?, ?)") + .bind(torrent_id) + .bind(node.0.clone()) + .bind(node.1) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_nodes_result { + drop(tx.rollback().await); + return Err(e); + } + // add tags for tag_id in &metadata.tags { @@ -773,6 +800,15 @@ impl Database for Mysql { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_nodes_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentNode>("SELECT node_ip, node_port FROM torrust_torrent_nodes WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| (a.node_ip.to_string(), a.node_port)).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/databases/sqlite.rs b/src/databases/sqlite.rs index 5fe1d398..e6fb01f5 100644 --- a/src/databases/sqlite.rs +++ b/src/databases/sqlite.rs @@ -13,7 +13,9 @@ use crate::models::category::CategoryId; use crate::models::info_hash::InfoHash; use crate::models::response::TorrentsResponse; use crate::models::torrent::{Metadata, TorrentListing}; -use crate::models::torrent_file::{DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, Torrent, TorrentFile}; +use crate::models::torrent_file::{ + DbTorrent, DbTorrentAnnounceUrl, DbTorrentFile, DbTorrentHttpSeedUrl, DbTorrentNode, Torrent, TorrentFile, +}; use crate::models::torrent_tag::{TagId, TorrentTag}; use crate::models::tracker_key::TrackerKey; use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile}; @@ -600,6 +602,31 @@ impl Database for Sqlite { return Err(e); } + // add nodes + + let insert_torrent_nodes_result: Result<(), database::Error> = if let Some(nodes) = &torrent.nodes { + for node in nodes { + let () = query("INSERT INTO torrust_torrent_nodes (torrent_id, node_ip, node_port) VALUES (?, ?, ?)") + .bind(torrent_id) + .bind(node.0.clone()) + .bind(node.1) + .execute(&mut *tx) + .await + .map(|_| ()) + .map_err(|_| database::Error::Error)?; + } + + Ok(()) + } else { + Ok(()) + }; + + // rollback transaction on error + if let Err(e) = insert_torrent_nodes_result { + drop(tx.rollback().await); + return Err(e); + } + // add tags for tag_id in &metadata.tags { @@ -767,6 +794,15 @@ impl Database for Sqlite { .map_err(|_| database::Error::TorrentNotFound) } + async fn get_torrent_nodes_from_id(&self, torrent_id: i64) -> Result, database::Error> { + query_as::<_, DbTorrentNode>("SELECT node_ip, node_port FROM torrust_torrent_nodes WHERE torrent_id = ?") + .bind(torrent_id) + .fetch_all(&self.pool) + .await + .map(|v| v.iter().map(|a| (a.node_ip.to_string(), a.node_port)).collect()) + .map_err(|_| database::Error::TorrentNotFound) + } + async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result { query_as::<_, TorrentListing>( "SELECT diff --git a/src/models/torrent_file.rs b/src/models/torrent_file.rs index 8c0503d9..64fa5e89 100644 --- a/src/models/torrent_file.rs +++ b/src/models/torrent_file.rs @@ -75,6 +75,7 @@ impl Torrent { torrent_files: &[TorrentFile], torrent_announce_urls: Vec>, torrent_http_seed_urls: Vec, + torrent_nodes: Vec<(String, i64)>, ) -> Self { let info_dict = TorrentInfoDictionary::with( &db_torrent.name, @@ -88,7 +89,7 @@ impl Torrent { Self { info: info_dict, announce: None, - nodes: None, + nodes: if torrent_nodes.is_empty() { None } else { Some(torrent_nodes) }, encoding: db_torrent.encoding.clone(), httpseeds: if torrent_http_seed_urls.is_empty() { None @@ -359,6 +360,12 @@ pub struct DbTorrentHttpSeedUrl { pub seed_url: String, } +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] +pub struct DbTorrentNode { + pub node_ip: String, + pub node_port: i64, +} + #[cfg(test)] mod tests {