torrust_index/databases/
database.rs

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