@@ -12,6 +12,7 @@ use crate::models::info_hash::InfoHash;
1212use crate :: models:: response:: TorrentsResponse ;
1313use crate :: models:: torrent:: TorrentListing ;
1414use crate :: models:: torrent_file:: { DbTorrentAnnounceUrl , DbTorrentFile , DbTorrentInfo , Torrent , TorrentFile } ;
15+ use crate :: models:: torrent_tag:: { TagId , TorrentTag } ;
1516use crate :: models:: tracker_key:: TrackerKey ;
1617use crate :: models:: user:: { User , UserAuthentication , UserCompact , UserId , UserProfile } ;
1718use crate :: utils:: clock;
@@ -300,6 +301,7 @@ impl Database for Mysql {
300301 & self ,
301302 search : & Option < String > ,
302303 categories : & Option < Vec < String > > ,
304+ tags : & Option < Vec < String > > ,
303305 sort : & Sorting ,
304306 offset : u64 ,
305307 limit : u8 ,
@@ -345,11 +347,36 @@ impl Database for Mysql {
345347 String :: new ( )
346348 } ;
347349
350+ let tag_filter_query = if let Some ( t) = tags {
351+ let mut i = 0 ;
352+ let mut tag_filters = String :: new ( ) ;
353+ for tag in t. iter ( ) {
354+ // don't take user input in the db query
355+ if let Ok ( sanitized_tag) = self . get_tag_from_name ( tag) . await {
356+ let mut str = format ! ( "tl.tag_id = '{}'" , sanitized_tag. tag_id) ;
357+ if i > 0 {
358+ str = format ! ( " OR {str}" ) ;
359+ }
360+ tag_filters. push_str ( & str) ;
361+ i += 1 ;
362+ }
363+ }
364+ if tag_filters. is_empty ( ) {
365+ String :: new ( )
366+ } else {
367+ format ! ( "INNER JOIN torrust_torrent_tag_links tl ON tt.torrent_id = tl.torrent_id AND ({tag_filters}) " )
368+ }
369+ } else {
370+ String :: new ( )
371+ } ;
372+
348373 let mut query_string = format ! (
349374 "SELECT tt.torrent_id, tp.username AS uploader, tt.info_hash, ti.title, ti.description, tt.category_id, DATE_FORMAT(tt.date_uploaded, '%Y-%m-%d %H:%i:%s') AS date_uploaded, tt.size AS file_size,
350375 CAST(COALESCE(sum(ts.seeders),0) as signed) as seeders,
351376 CAST(COALESCE(sum(ts.leechers),0) as signed) as leechers
352- FROM torrust_torrents tt {category_filter_query}
377+ FROM torrust_torrents tt
378+ {category_filter_query}
379+ {tag_filter_query}
353380 INNER JOIN torrust_user_profiles tp ON tt.uploader_id = tp.user_id
354381 INNER JOIN torrust_torrent_info ti ON tt.torrent_id = ti.torrent_id
355382 LEFT JOIN torrust_torrent_tracker_stats ts ON tt.torrent_id = ts.torrent_id
@@ -677,6 +704,103 @@ impl Database for Mysql {
677704 } )
678705 }
679706
707+ async fn add_tag ( & self , name : & str ) -> Result < ( ) , database:: Error > {
708+ query ( "INSERT INTO torrust_torrent_tags (name) VALUES (?)" )
709+ . bind ( name)
710+ . execute ( & self . pool )
711+ . await
712+ . map ( |_| ( ) )
713+ . map_err ( |err| database:: Error :: ErrorWithText ( err. to_string ( ) ) )
714+ }
715+
716+ async fn delete_tag ( & self , tag_id : TagId ) -> Result < ( ) , database:: Error > {
717+ query ( "DELETE FROM torrust_torrent_tags WHERE tag_id = ?" )
718+ . bind ( tag_id)
719+ . execute ( & self . pool )
720+ . await
721+ . map ( |_| ( ) )
722+ . map_err ( |err| database:: Error :: ErrorWithText ( err. to_string ( ) ) )
723+ }
724+
725+ async fn add_torrent_tag_link ( & self , torrent_id : i64 , tag_id : TagId ) -> Result < ( ) , database:: Error > {
726+ query ( "INSERT INTO torrust_torrent_tag_links (torrent_id, tag_id) VALUES (?, ?)" )
727+ . bind ( torrent_id)
728+ . bind ( tag_id)
729+ . execute ( & self . pool )
730+ . await
731+ . map ( |_| ( ) )
732+ . map_err ( |_| database:: Error :: Error )
733+ }
734+
735+ async fn add_torrent_tag_links ( & self , torrent_id : i64 , tag_ids : & Vec < TagId > ) -> Result < ( ) , database:: Error > {
736+ let mut transaction = self
737+ . pool
738+ . begin ( )
739+ . await
740+ . map_err ( |err| database:: Error :: ErrorWithText ( err. to_string ( ) ) ) ?;
741+
742+ for tag_id in tag_ids {
743+ query ( "INSERT INTO torrust_torrent_tag_links (torrent_id, tag_id) VALUES (?, ?)" )
744+ . bind ( torrent_id)
745+ . bind ( tag_id)
746+ . execute ( & mut transaction)
747+ . await
748+ . map_err ( |err| database:: Error :: ErrorWithText ( err. to_string ( ) ) ) ?;
749+ }
750+
751+ transaction
752+ . commit ( )
753+ . await
754+ . map_err ( |err| database:: Error :: ErrorWithText ( err. to_string ( ) ) )
755+ }
756+
757+ async fn delete_torrent_tag_link ( & self , torrent_id : i64 , tag_id : TagId ) -> Result < ( ) , database:: Error > {
758+ query ( "DELETE FROM torrust_torrent_tag_links WHERE torrent_id = ? AND tag_id = ?" )
759+ . bind ( torrent_id)
760+ . bind ( tag_id)
761+ . execute ( & self . pool )
762+ . await
763+ . map ( |_| ( ) )
764+ . map_err ( |_| database:: Error :: Error )
765+ }
766+
767+ async fn delete_all_torrent_tag_links ( & self , torrent_id : i64 ) -> Result < ( ) , database:: Error > {
768+ query ( "DELETE FROM torrust_torrent_tag_links WHERE torrent_id = ?" )
769+ . bind ( torrent_id)
770+ . execute ( & self . pool )
771+ . await
772+ . map ( |_| ( ) )
773+ . map_err ( |err| database:: Error :: ErrorWithText ( err. to_string ( ) ) )
774+ }
775+
776+ async fn get_tag_from_name ( & self , name : & str ) -> Result < TorrentTag , database:: Error > {
777+ query_as :: < _ , TorrentTag > ( "SELECT tag_id, name FROM torrust_torrent_tags WHERE name = ?" )
778+ . bind ( name)
779+ . fetch_one ( & self . pool )
780+ . await
781+ . map_err ( |err| database:: Error :: TagNotFound )
782+ }
783+
784+ async fn get_tags ( & self ) -> Result < Vec < TorrentTag > , database:: Error > {
785+ query_as :: < _ , TorrentTag > ( "SELECT tag_id, name FROM torrust_torrent_tags" )
786+ . fetch_all ( & self . pool )
787+ . await
788+ . map_err ( |_| database:: Error :: Error )
789+ }
790+
791+ async fn get_tags_for_torrent_id ( & self , torrent_id : i64 ) -> Result < Vec < TorrentTag > , database:: Error > {
792+ query_as :: < _ , TorrentTag > (
793+ "SELECT torrust_torrent_tags.tag_id, torrust_torrent_tags.name
794+ FROM torrust_torrent_tags
795+ JOIN torrust_torrent_tag_links ON torrust_torrent_tags.tag_id = torrust_torrent_tag_links.tag_id
796+ WHERE torrust_torrent_tag_links.torrent_id = ?" ,
797+ )
798+ . bind ( torrent_id)
799+ . fetch_all ( & self . pool )
800+ . await
801+ . map_err ( |_| database:: Error :: Error )
802+ }
803+
680804 async fn update_tracker_info (
681805 & self ,
682806 torrent_id : i64 ,
0 commit comments