torrust_index_backend/databases/
database.rs

1use async_trait::async_trait;
2use chrono::NaiveDateTime;
3use serde::{Deserialize, Serialize};
4
5use crate::databases::mysql::Mysql;
6use crate::databases::sqlite::Sqlite;
7use crate::models::category::CategoryId;
8use crate::models::info_hash::InfoHash;
9use crate::models::response::TorrentsResponse;
10use crate::models::torrent::TorrentListing;
11use crate::models::torrent_file::{DbTorrentInfo, Torrent, TorrentFile};
12use crate::models::torrent_tag::{TagId, TorrentTag};
13use crate::models::tracker_key::TrackerKey;
14use crate::models::user::{User, UserAuthentication, UserCompact, UserId, UserProfile};
15
16/// Database tables to be truncated when upgrading from v1.0.0 to v2.0.0.
17/// They must be in the correct order to avoid foreign key errors.
18pub const TABLES_TO_TRUNCATE: &[&str] = &[
19    "torrust_torrent_announce_urls",
20    "torrust_torrent_files",
21    "torrust_torrent_info",
22    "torrust_torrent_tag_links",
23    "torrust_torrent_tracker_stats",
24    "torrust_torrents",
25    "torrust_tracker_keys",
26    "torrust_user_authentication",
27    "torrust_user_bans",
28    "torrust_user_invitation_uses",
29    "torrust_user_invitations",
30    "torrust_user_profiles",
31    "torrust_user_public_keys",
32    "torrust_users",
33    "torrust_categories",
34    "torrust_torrent_tags",
35];
36
37/// Database drivers.
38#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
39pub enum Driver {
40    Sqlite3,
41    Mysql,
42}
43
44/// Compact representation of torrent.
45#[derive(Debug, Serialize, sqlx::FromRow)]
46pub struct TorrentCompact {
47    pub torrent_id: i64,
48    pub info_hash: String,
49}
50
51/// Torrent category.
52#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
53pub struct Category {
54    pub category_id: i64,
55    pub name: String,
56    pub num_torrents: i64,
57}
58
59/// Sorting options for torrents.
60#[derive(Clone, Copy, Debug, Deserialize)]
61pub enum Sorting {
62    UploadedAsc,
63    UploadedDesc,
64    SeedersAsc,
65    SeedersDesc,
66    LeechersAsc,
67    LeechersDesc,
68    NameAsc,
69    NameDesc,
70    SizeAsc,
71    SizeDesc,
72}
73
74/// Database errors.
75#[derive(Debug)]
76pub enum Error {
77    Error,
78    ErrorWithText(String),
79    UnrecognizedDatabaseDriver, // when the db path does not start with sqlite or mysql
80    UsernameTaken,
81    EmailTaken,
82    UserNotFound,
83    CategoryAlreadyExists,
84    CategoryNotFound,
85    TagAlreadyExists,
86    TagNotFound,
87    TorrentNotFound,
88    TorrentAlreadyExists, // when uploading an already uploaded info_hash
89    TorrentTitleAlreadyExists,
90}
91
92/// Get the Driver of the Database from the Connection String
93///
94/// # Errors
95///
96/// This function will return an `Error::UnrecognizedDatabaseDriver` if unable to match database type.
97pub fn get_driver(db_path: &str) -> Result<Driver, Error> {
98    match &db_path.chars().collect::<Vec<char>>() as &[char] {
99        ['s', 'q', 'l', 'i', 't', 'e', ..] => Ok(Driver::Sqlite3),
100        ['m', 'y', 's', 'q', 'l', ..] => Ok(Driver::Mysql),
101        _ => Err(Error::UnrecognizedDatabaseDriver),
102    }
103}
104
105/// Connect to a database.
106///
107/// # Errors
108///
109/// This function will return an `Error::UnrecognizedDatabaseDriver` if unable to match database type.
110pub async fn connect(db_path: &str) -> Result<Box<dyn Database>, Error> {
111    let db_driver = self::get_driver(db_path)?;
112
113    Ok(match db_driver {
114        self::Driver::Sqlite3 => Box::new(Sqlite::new(db_path).await),
115        self::Driver::Mysql => Box::new(Mysql::new(db_path).await),
116    })
117}
118
119/// Trait for database implementations.
120#[async_trait]
121pub trait Database: Sync + Send {
122    /// Return current database driver.
123    fn get_database_driver(&self) -> Driver;
124
125    async fn new(db_path: &str) -> Self
126    where
127        Self: Sized;
128
129    /// Add new user and return the newly inserted `user_id`.
130    async fn insert_user_and_get_id(&self, username: &str, email: &str, password: &str) -> Result<UserId, Error>;
131
132    /// Get `User` from `user_id`.
133    async fn get_user_from_id(&self, user_id: i64) -> Result<User, Error>;
134
135    /// Get `UserAuthentication` from `user_id`.
136    async fn get_user_authentication_from_id(&self, user_id: UserId) -> Result<UserAuthentication, Error>;
137
138    /// Get `UserProfile` from `username`.
139    async fn get_user_profile_from_username(&self, username: &str) -> Result<UserProfile, Error>;
140
141    /// Get `UserCompact` from `user_id`.
142    async fn get_user_compact_from_id(&self, user_id: i64) -> Result<UserCompact, Error>;
143
144    /// Get a user's `TrackerKey`.
145    async fn get_user_tracker_key(&self, user_id: i64) -> Option<TrackerKey>;
146
147    /// Get total user count.
148    async fn count_users(&self) -> Result<i64, Error>;
149
150    /// Ban user with `user_id`, `reason` and `date_expiry`.
151    async fn ban_user(&self, user_id: i64, reason: &str, date_expiry: NaiveDateTime) -> Result<(), Error>;
152
153    /// Grant a user the administrator role.
154    async fn grant_admin_role(&self, user_id: i64) -> Result<(), Error>;
155
156    /// Verify a user's email with `user_id`.
157    async fn verify_email(&self, user_id: i64) -> Result<(), Error>;
158
159    /// Link a `TrackerKey` to a certain user with `user_id`.
160    async fn add_tracker_key(&self, user_id: i64, tracker_key: &TrackerKey) -> Result<(), Error>;
161
162    /// Delete user and all related user data with `user_id`.
163    async fn delete_user(&self, user_id: i64) -> Result<(), Error>;
164
165    /// Add a new category and return `category_id`.
166    async fn insert_category_and_get_id(&self, category_name: &str) -> Result<i64, Error>;
167
168    /// Get `Category` from `category_id`.
169    async fn get_category_from_id(&self, category_id: i64) -> Result<Category, Error>;
170
171    /// Get `Category` from `category_name`.
172    async fn get_category_from_name(&self, category_name: &str) -> Result<Category, Error>;
173
174    /// Get all categories as `Vec<Category>`.
175    async fn get_categories(&self) -> Result<Vec<Category>, Error>;
176
177    /// Delete category with `category_name`.
178    async fn delete_category(&self, category_name: &str) -> Result<(), Error>;
179
180    /// Get results of a torrent search in a paginated and sorted form as `TorrentsResponse` from `search`, `categories`, `sort`, `offset` and `page_size`.
181    async fn get_torrents_search_sorted_paginated(
182        &self,
183        search: &Option<String>,
184        categories: &Option<Vec<String>>,
185        tags: &Option<Vec<String>>,
186        sort: &Sorting,
187        offset: u64,
188        page_size: u8,
189    ) -> Result<TorrentsResponse, Error>;
190
191    /// Add new torrent and return the newly inserted `torrent_id` with `torrent`, `uploader_id`, `category_id`, `title` and `description`.
192    async fn insert_torrent_and_get_id(
193        &self,
194        torrent: &Torrent,
195        uploader_id: UserId,
196        category_id: i64,
197        title: &str,
198        description: &str,
199    ) -> Result<i64, Error>;
200
201    /// Get `Torrent` from `InfoHash`.
202    async fn get_torrent_from_info_hash(&self, info_hash: &InfoHash) -> Result<Torrent, Error> {
203        let torrent_info = self.get_torrent_info_from_info_hash(info_hash).await?;
204
205        let torrent_files = self.get_torrent_files_from_id(torrent_info.torrent_id).await?;
206
207        let torrent_announce_urls = self.get_torrent_announce_urls_from_id(torrent_info.torrent_id).await?;
208
209        Ok(Torrent::from_db_info_files_and_announce_urls(
210            torrent_info,
211            torrent_files,
212            torrent_announce_urls,
213        ))
214    }
215
216    /// Get `Torrent` from `torrent_id`.
217    async fn get_torrent_from_id(&self, torrent_id: i64) -> Result<Torrent, Error> {
218        let torrent_info = self.get_torrent_info_from_id(torrent_id).await?;
219
220        let torrent_files = self.get_torrent_files_from_id(torrent_id).await?;
221
222        let torrent_announce_urls = self.get_torrent_announce_urls_from_id(torrent_id).await?;
223
224        Ok(Torrent::from_db_info_files_and_announce_urls(
225            torrent_info,
226            torrent_files,
227            torrent_announce_urls,
228        ))
229    }
230
231    /// Get torrent's info as `DbTorrentInfo` from `torrent_id`.
232    async fn get_torrent_info_from_id(&self, torrent_id: i64) -> Result<DbTorrentInfo, Error>;
233
234    /// Get torrent's info as `DbTorrentInfo` from torrent `InfoHash`.
235    async fn get_torrent_info_from_info_hash(&self, info_hash: &InfoHash) -> Result<DbTorrentInfo, Error>;
236
237    /// Get all torrent's files as `Vec<TorrentFile>` from `torrent_id`.
238    async fn get_torrent_files_from_id(&self, torrent_id: i64) -> Result<Vec<TorrentFile>, Error>;
239
240    /// Get all torrent's announce urls as `Vec<Vec<String>>` from `torrent_id`.
241    async fn get_torrent_announce_urls_from_id(&self, torrent_id: i64) -> Result<Vec<Vec<String>>, Error>;
242
243    /// Get `TorrentListing` from `torrent_id`.
244    async fn get_torrent_listing_from_id(&self, torrent_id: i64) -> Result<TorrentListing, Error>;
245
246    /// Get `TorrentListing` from `InfoHash`.
247    async fn get_torrent_listing_from_info_hash(&self, info_hash: &InfoHash) -> Result<TorrentListing, Error>;
248
249    /// Get all torrents as `Vec<TorrentCompact>`.
250    async fn get_all_torrents_compact(&self) -> Result<Vec<TorrentCompact>, Error>;
251
252    /// Update a torrent's title with `torrent_id` and `title`.
253    async fn update_torrent_title(&self, torrent_id: i64, title: &str) -> Result<(), Error>;
254
255    /// Update a torrent's description with `torrent_id` and `description`.
256    async fn update_torrent_description(&self, torrent_id: i64, description: &str) -> Result<(), Error>;
257
258    /// Update a torrent's category with `torrent_id` and `category_id`.
259    async fn update_torrent_category(&self, torrent_id: i64, category_id: CategoryId) -> Result<(), Error>;
260
261    /// Add a new tag.
262    async fn add_tag(&self, name: &str) -> Result<(), Error>;
263
264    /// Delete a tag.
265    async fn delete_tag(&self, tag_id: TagId) -> Result<(), Error>;
266
267    /// Add a tag to torrent.
268    async fn add_torrent_tag_link(&self, torrent_id: i64, tag_id: TagId) -> Result<(), Error>;
269
270    /// Add multiple tags to a torrent at once.
271    async fn add_torrent_tag_links(&self, torrent_id: i64, tag_ids: &[TagId]) -> Result<(), Error>;
272
273    /// Remove a tag from torrent.
274    async fn delete_torrent_tag_link(&self, torrent_id: i64, tag_id: TagId) -> Result<(), Error>;
275
276    /// Remove all tags from torrent.
277    async fn delete_all_torrent_tag_links(&self, torrent_id: i64) -> Result<(), Error>;
278
279    /// Get tag from name.
280    async fn get_tag_from_name(&self, name: &str) -> Result<TorrentTag, Error>;
281
282    /// Get all tags as `Vec<TorrentTag>`.
283    async fn get_tags(&self) -> Result<Vec<TorrentTag>, Error>;
284
285    /// Get tags for `torrent_id`.
286    async fn get_tags_for_torrent_id(&self, torrent_id: i64) -> Result<Vec<TorrentTag>, Error>;
287
288    /// Update the seeders and leechers info for a torrent with `torrent_id`, `tracker_url`, `seeders` and `leechers`.
289    async fn update_tracker_info(&self, torrent_id: i64, tracker_url: &str, seeders: i64, leechers: i64) -> Result<(), Error>;
290
291    /// Delete a torrent with `torrent_id`.
292    async fn delete_torrent(&self, torrent_id: i64) -> Result<(), Error>;
293
294    /// DELETES ALL DATABASE ROWS, ONLY CALL THIS IF YOU KNOW WHAT YOU'RE DOING!
295    async fn delete_all_database_rows(&self) -> Result<(), Error>;
296}