diff --git a/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent new file mode 100644 index 00000000..1a08a811 Binary files /dev/null and b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent differ diff --git a/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent.json b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent.json new file mode 100644 index 00000000..caaa1a41 --- /dev/null +++ b/docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent.json @@ -0,0 +1,10 @@ +{ + "created by": "qBittorrent v4.4.1", + "creation date": 1679674628, + "info": { + "length": 172204, + "name": "mandelbrot_2048x2048.png", + "piece length": 16384, + "pieces": "7D 91 71 0D 9D 4D BA 88 9B 54 20 54 D5 26 72 8D 5A 86 3F E1 21 DF 77 C7 F7 BB 6C 77 96 21 66 25 38 C5 D9 CD AB 8B 08 EF 8C 24 9B B2 F5 C4 CD 2A DF 0B C0 0C F0 AD DF 72 90 E5 B6 41 4C 23 6C 47 9B 8E 9F 46 AA 0C 0D 8E D1 97 FF EE 68 8B 5F 34 A3 87 D7 71 C5 A6 F9 8E 2E A6 31 7C BD F0 F9 E2 23 F9 CC 80 AF 54 00 04 F9 85 69 1C 77 89 C1 76 4E D6 AA BF 61 A6 C2 80 99 AB B6 5F 60 2F 40 A8 25 BE 32 A3 3D 9D 07 0C 79 68 98 D4 9D 63 49 AF 20 58 66 26 6F 98 6B 6D 32 34 CD 7D 08 15 5E 1A D0 00 09 57 AB 30 3B 20 60 C1 DC 12 87 D6 F3 E7 45 4F 70 67 09 36 31 55 F2 20 F6 6C A5 15 6F 2C 89 95 69 16 53 81 7D 31 F1 B6 BD 37 42 CC 11 0B B2 FC 2B 49 A5 85 B6 FC 76 74 44 93" + } +} \ No newline at end of file diff --git a/project-words.txt b/project-words.txt index 289364ad..5ca22cd6 100644 --- a/project-words.txt +++ b/project-words.txt @@ -3,6 +3,7 @@ addrs AUTOINCREMENT bencode bencoded +Benoit binascii btih chrono @@ -21,6 +22,7 @@ hexlify httpseeds imagoodboy imdl +indexadmin indexmap infohash jsonwebtoken @@ -30,6 +32,7 @@ LEECHERS lettre luckythelab mailcatcher +mandelbrotset metainfo nanos NCCA @@ -38,6 +41,7 @@ nocapture Oberhachingerstr oneshot ppassword +proxied reqwest Roadmap ROADMAP @@ -54,6 +58,8 @@ tempfile thiserror torrust Torrust +unban +Ununauthorized upgrader Uragqm urlencoding diff --git a/src/cache/image/manager.rs b/src/cache/image/manager.rs index 9e8d814c..40367ca9 100644 --- a/src/cache/image/manager.rs +++ b/src/cache/image/manager.rs @@ -153,6 +153,8 @@ impl ImageCacheService { .await .map_err(|_| Error::UrlIsUnreachable)?; + // code-review: we could get a HTTP 304 response, which doesn't contain a body (the image bytes). + if let Some(content_type) = res.headers().get("Content-Type") { if content_type != "image/jpeg" && content_type != "image/png" { return Err(Error::UrlIsNotAnImage); diff --git a/src/lib.rs b/src/lib.rs index 03213e05..c66d8e73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,12 @@ +//! Documentation for [Torrust Tracker Index Backend](https://github.com/torrust/torrust-index-backend) API. +//! +//! This is the backend API for [Torrust Tracker Index](https://github.com/torrust/torrust-index). +//! +//! It is written in Rust and uses the actix-web framework. It is designed to be +//! used with by the [Torrust Tracker Index Frontend](https://github.com/torrust/torrust-index-frontend). +//! +//! If you are looking for information on how to use the API, please see the +//! [API v1](crate::web::api::v1) section of the documentation. pub mod app; pub mod auth; pub mod bootstrap; @@ -15,6 +24,7 @@ pub mod tracker; pub mod ui; pub mod upgrades; pub mod utils; +pub mod web; trait AsCSV { fn as_csv(&self) -> Result>, ()> diff --git a/src/web/api/mod.rs b/src/web/api/mod.rs new file mode 100644 index 00000000..8582ba66 --- /dev/null +++ b/src/web/api/mod.rs @@ -0,0 +1,6 @@ +//! The Torrust Index Backend API. +//! +//! Currently, the API has only one version: `v1`. +//! +//! Refer to the [`v1`](crate::web::api::v1) module for more information. +pub mod v1; diff --git a/src/web/api/v1/auth.rs b/src/web/api/v1/auth.rs new file mode 100644 index 00000000..b84adee9 --- /dev/null +++ b/src/web/api/v1/auth.rs @@ -0,0 +1,80 @@ +//! API authentication. +//! +//! The API uses a [bearer token authentication scheme](https://datatracker.ietf.org/doc/html/rfc6750). +//! +//! API clients must have an account on the website to be able to use the API. +//! +//! # Authentication flow +//! +//! - [Registration](#registration) +//! - [Login](#login) +//! - [Using the token](#using-the-token) +//! +//! ## Registration +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request POST \ +//! --data '{"username":"indexadmin","email":"indexadmin@torrust.com","password":"BenoitMandelbrot1924","confirm_password":"BenoitMandelbrot1924"}' \ +//! http://127.0.0.1:3000/v1/user/register +//! ``` +//! +//! **NOTICE**: The first user is automatically an administrator. Currently, +//! there is no way to change this. There is one administrator per instance. +//! And you cannot delete the administrator account or make another user an +//! administrator. For testing purposes, you can create a new administrator +//! account by creating a new user and then manually changing the `administrator` +//! field in the `torrust_users` table to `1`. +//! +//! ## Login +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request POST \ +//! --data '{"login":"indexadmin","password":"BenoitMandelbrot1924"}' \ +//! http://127.0.0.1:3000/v1/user/login +//! ``` +//! +//! **Response** +//! +//! ```json +//! { +//! "data":{ +//! "token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI", +//! "username":"indexadmin", +//! "admin":true +//! } +//! } +//! ``` +//! +//! **NOTICE**: The token is valid for 2 weeks (`1_209_600` seconds). After that, +//! you will have to renew the token. +//! +//! **NOTICE**: The token is associated with the user role. If you change the +//! user's role, you will have to log in again to get a new token with the new +//! role. +//! +//! ## Using the token +//! +//! Some endpoints require authentication. To use the token, you must add the +//! `Authorization` header to your request. For example, if you want to add a +//! new category, you must do the following: +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request POST \ +//! --data '{"name":"new category","icon":null}' \ +//! http://127.0.0.1:3000/v1/category +//! ``` +//! +//! **Response** +//! +//! ```json +//! { +//! "data": "new category" +//! } +//! ``` diff --git a/src/web/api/v1/contexts/about/mod.rs b/src/web/api/v1/contexts/about/mod.rs new file mode 100644 index 00000000..0b12ff66 --- /dev/null +++ b/src/web/api/v1/contexts/about/mod.rs @@ -0,0 +1,86 @@ +//! API context: `about`. +//! +//! This API context is responsible for providing metadata about the API. +//! +//! # Endpoints +//! +//! - [About](#about) +//! - [License](#license) +//! +//! # About +//! +//! `GET /v1/about` +//! +//! Returns a html page with information about the API. +//! +//! +//! **Example request** +//! +//! ```bash +//! curl "http://127.0.0.1:3000/v1/about" +//! ``` +//! +//! **Example response** `200` +//! +//! ```html +//! +//! +//! About +//! +//! +//!

Torrust Index Backend

+//! +//!

About

+//! +//!

Hi! This is a running torrust-index-backend.

+//! +//! +//! +//! ``` +//! +//! # License +//! +//! `GET /v1/about/license` +//! +//! Returns the API license. +//! +//! **Example request** +//! +//! ```bash +//! curl "http://127.0.0.1:3000/v1/about/license" +//! ``` +//! +//! **Example response** `200` +//! +//! ```html +//! +//! +//! Licensing +//! +//! +//!

Torrust Index Backend

+//! +//!

Licensing

+//! +//!

Multiple Licenses

+//! +//!

+//! This repository has multiple licenses depending on the content type, the date of contributions or stemming from external component licenses that were not developed by any of Torrust team members or Torrust repository +//! contributors. +//!

+//! +//!

The two main applicable license to most of its content are:

+//! +//!

- For Code -- agpl-3.0

+//! +//!

- For Media (Images, etc.) -- cc-by-sa

+//! +//!

If you want to read more about all the licenses and how they apply please refer to the contributor agreement.

+//! +//! +//! +//! ``` diff --git a/src/web/api/v1/contexts/category/mod.rs b/src/web/api/v1/contexts/category/mod.rs new file mode 100644 index 00000000..68cf07e3 --- /dev/null +++ b/src/web/api/v1/contexts/category/mod.rs @@ -0,0 +1,140 @@ +//! API context: `category`. +//! +//! This API context is responsible for handling torrent categories. +//! +//! # Endpoints +//! +//! - [Get all categories](#get-all-categories) +//! - [Add a category](#add-a-category) +//! - [Delete a category](#delete-a-category) +//! +//! **NOTICE**: We don't support multiple languages yet, so the category name +//! is always in English. +//! +//! # Get all categories +//! +//! `GET /v1/category` +//! +//! Returns all torrent categories. +//! +//! **Example request** +//! +//! ```bash +//! curl "http://127.0.0.1:3000/v1/category" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": [ +//! { +//! "category_id": 3, +//! "name": "games", +//! "num_torrents": 0 +//! }, +//! { +//! "category_id": 1, +//! "name": "movies", +//! "num_torrents": 0 +//! }, +//! { +//! "category_id": 4, +//! "name": "music", +//! "num_torrents": 0 +//! }, +//! { +//! "category_id": 5, +//! "name": "software", +//! "num_torrents": 0 +//! }, +//! { +//! "category_id": 2, +//! "name": "tv shows", +//! "num_torrents": 0 +//! } +//! ] +//! } +//! ``` +//! **Resource** +//! +//! Refer to the [`Category`](crate::databases::database::Category) +//! struct for more information about the response attributes. +//! +//! # Add a category +//! +//! `POST /v1/category` +//! +//! It adds a new category. +//! +//! **POST params** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `name` | `String` | The name of the category | Yes | `new category` +//! `icon` | `Option` | Icon representing the category | No | +//! +//! **Notice**: the `icon` field is not implemented yet. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request POST \ +//! --data '{"name":"new category","icon":null}' \ +//! http://127.0.0.1:3000/v1/category +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": "new category" +//! } +//! ``` +//! +//! **Resource** +//! +//! Refer to [`OkResponse`](crate::models::response::OkResponse) for more +//! information about the response attributes. The response contains only the +//! name of the newly created category. +//! +//! # Delete a category +//! +//! `DELETE /v1/category` +//! +//! It deletes a category. +//! +//! **POST params** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `name` | `String` | The name of the category | Yes | `new category` +//! `icon` | `Option` | Icon representing the category | No | +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request DELETE \ +//! --data '{"name":"new category","icon":null}' \ +//! http://127.0.0.1:3000/v1/category +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": "new category" +//! } +//! ``` +//! +//! **Resource** +//! +//! Refer to [`OkResponse`](crate::models::response::OkResponse) for more +//! information about the response attributes. The response contains only the +//! name of the deleted category. diff --git a/src/web/api/v1/contexts/mod.rs b/src/web/api/v1/contexts/mod.rs new file mode 100644 index 00000000..7119d40d --- /dev/null +++ b/src/web/api/v1/contexts/mod.rs @@ -0,0 +1,17 @@ +//! The API is organized in the following contexts: +//! +//! Context | Description | Version +//! ---|---|--- +//! `About` | Metadata about the API | [`v1`](crate::web::api::v1::contexts::about) +//! `Category` | Torrent categories | [`v1`](crate::web::api::v1::contexts::category) +//! `Proxy` | Image proxy cache | [`v1`](crate::web::api::v1::contexts::proxy) +//! `Settings` | Index settings | [`v1`](crate::web::api::v1::contexts::settings) +//! `Torrent` | Indexed torrents | [`v1`](crate::web::api::v1::contexts::torrent) +//! `User` | Users | [`v1`](crate::web::api::v1::contexts::user) +//! +pub mod about; +pub mod category; +pub mod proxy; +pub mod settings; +pub mod torrent; +pub mod user; diff --git a/src/web/api/v1/contexts/proxy/mod.rs b/src/web/api/v1/contexts/proxy/mod.rs new file mode 100644 index 00000000..29eb0879 --- /dev/null +++ b/src/web/api/v1/contexts/proxy/mod.rs @@ -0,0 +1,43 @@ +//! API context: `proxy`. +//! +//! This context contains the API routes for the proxy service. +//! +//! The torrent descriptions can contain images. These images are proxied +//! through the backend to: +//! +//! - Prevent leaking the user's IP address. +//! - Avoid storing images on the server. +//! +//! The proxy service is a simple cache that stores the images in memory. +//! +//! **NOTICE:** The proxy service is not intended to be used as a general +//! purpose proxy. It is only intended to be used for the images in the +//! torrent descriptions. +//! +//! **NOTICE:** Ununauthorized users can't see images. They will get an image +//! with the text "Sign in to see image" instead. +//! +//! # Example +//! +//! For unauthenticated clients: +//! +//! ```bash +//! curl \ +//! --header "cache-control: no-cache" \ +//! --header "pragma: no-cache" \ +//! --output mandelbrotset.jpg \ +//! http://0.0.0.0:3000/v1/proxy/image/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2F2%2F21%2FMandel_zoom_00_mandelbrot_set.jpg%2F1280px-Mandel_zoom_00_mandelbrot_set.jpg +//! ``` +//! +//! You will receive an image with the text "Sign in to see image" instead. +//! +//! For authenticated clients: +//! +//! ```bash +//! curl \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --header "cache-control: no-cache" \ +//! --header "pragma: no-cache" \ +//! --output mandelbrotset.jpg \ +//! http://0.0.0.0:3000/v1/proxy/image/https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fthumb%2F2%2F21%2FMandel_zoom_00_mandelbrot_set.jpg%2F1280px-Mandel_zoom_00_mandelbrot_set.jpg +//! ``` diff --git a/src/web/api/v1/contexts/settings/mod.rs b/src/web/api/v1/contexts/settings/mod.rs new file mode 100644 index 00000000..70cd94b2 --- /dev/null +++ b/src/web/api/v1/contexts/settings/mod.rs @@ -0,0 +1,169 @@ +//! API context: `settings`. +//! +//! This API context is responsible for handling the application settings. +//! +//! # Endpoints +//! +//! - [Get all settings](#get-all-settings) +//! - [Update all settings](#update-all-settings) +//! - [Get site name](#get-site-name) +//! - [Get public settings](#get-public-settings) +//! +//! # Get all settings +//! +//! `GET /v1/settings` +//! +//! Returns all settings. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request GET \ +//! "http://127.0.0.1:3000/v1/settings" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "website": { +//! "name": "Torrust" +//! }, +//! "tracker": { +//! "url": "udp://localhost:6969", +//! "mode": "Public", +//! "api_url": "http://localhost:1212", +//! "token": "MyAccessToken", +//! "token_valid_seconds": 7257600 +//! }, +//! "net": { +//! "port": 3000, +//! "base_url": null +//! }, +//! "auth": { +//! "email_on_signup": "Optional", +//! "min_password_length": 6, +//! "max_password_length": 64, +//! "secret_key": "MaxVerstappenWC2021" +//! }, +//! "database": { +//! "connect_url": "sqlite://./storage/database/data.db?mode=rwc" +//! }, +//! "mail": { +//! "email_verification_enabled": false, +//! "from": "example@email.com", +//! "reply_to": "noreply@email.com", +//! "username": "", +//! "password": "", +//! "server": "", +//! "port": 25 +//! }, +//! "image_cache": { +//! "max_request_timeout_ms": 1000, +//! "capacity": 128000000, +//! "entry_size_limit": 4000000, +//! "user_quota_period_seconds": 3600, +//! "user_quota_bytes": 64000000 +//! }, +//! "api": { +//! "default_torrent_page_size": 10, +//! "max_torrent_page_size": 30 +//! }, +//! "tracker_statistics_importer": { +//! "torrent_info_update_interval": 3600 +//! } +//! } +//! } +//! ``` +//! **Resource** +//! +//! Refer to the [`TorrustBackend`](crate::config::TorrustBackend) +//! struct for more information about the response attributes. +//! +//! # Update all settings +//! +//! **NOTICE**: This endpoint to update the settings does not work when you use +//! environment variables to configure the application. You need to use a +//! configuration file instead. Because settings are persisted in that file. +//! Refer to the issue [#144](https://github.com/torrust/torrust-index-backend/issues/144) +//! for more information. +//! +//! `POST /v1/settings` +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request POST \ +//! --data '{"website":{"name":"Torrust"},"tracker":{"url":"udp://localhost:6969","mode":"Public","api_url":"http://localhost:1212","token":"MyAccessToken","token_valid_seconds":7257600},"net":{"port":3000,"base_url":null},"auth":{"email_on_signup":"Optional","min_password_length":6,"max_password_length":64,"secret_key":"MaxVerstappenWC2021"},"database":{"connect_url":"sqlite://./storage/database/data.db?mode=rwc"},"mail":{"email_verification_enabled":false,"from":"example@email.com","reply_to":"noreply@email.com","username":"","password":"","server":"","port":25},"image_cache":{"max_request_timeout_ms":1000,"capacity":128000000,"entry_size_limit":4000000,"user_quota_period_seconds":3600,"user_quota_bytes":64000000},"api":{"default_torrent_page_size":10,"max_torrent_page_size":30},"tracker_statistics_importer":{"torrent_info_update_interval":3600}}' \ +//! "http://127.0.0.1:3000/v1/settings" +//! ``` +//! +//! The response contains the settings that were updated. +//! +//! **Resource** +//! +//! Refer to the [`TorrustBackend`](crate::config::TorrustBackend) +//! struct for more information about the response attributes. +//! +//! # Get site name +//! +//! `GET /v1/settings/name` +//! +//! It returns the name of the site. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request GET \ +//! "http://127.0.0.1:3000/v1/settings/name" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data":"Torrust" +//! } +//! ``` +//! +//! # Get public settings +//! +//! `GET /v1/settings/public` +//! +//! It returns all the public settings. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request GET \ +//! "http://127.0.0.1:3000/v1/settings/public" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "website_name": "Torrust", +//! "tracker_url": "udp://localhost:6969", +//! "tracker_mode": "Public", +//! "email_on_signup": "Optional" +//! } +//! } +//! ``` +//! +//! **Resource** +//! +//! Refer to the [`ConfigurationPublic`](crate::config::ConfigurationPublic) +//! struct for more information about the response attributes. diff --git a/src/web/api/v1/contexts/torrent/mod.rs b/src/web/api/v1/contexts/torrent/mod.rs new file mode 100644 index 00000000..77d08b8a --- /dev/null +++ b/src/web/api/v1/contexts/torrent/mod.rs @@ -0,0 +1,330 @@ +//! API context: `torrent`. +//! +//! This API context is responsible for handling all torrent related requests. +//! +//! # Endpoints +//! +//! - [Upload new torrent](#upload-new-torrent) +//! - [Download a torrent](#download-a-torrent) +//! - [Get torrent info](#get-torrent-info) +//! - [List torrent infos](#list-torrent-infos) +//! - [Update torrent info](#update-torrent-info) +//! - [Delete a torrent](#delete-a-torrent) +//! +//! # Upload new torrent +//! +//! `POST /v1/torrent/upload` +//! +//! It uploads a new torrent to the index. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: multipart/form-data" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request POST \ +//! --form "title=MandelbrotSet" \ +//! --form "description=MandelbrotSet image" \ +//! --form "category=software" \ +//! --form "torrent=@docs/media/mandelbrot_2048x2048_infohash_v1.png.torrent;type=application/x-bittorrent" \ +//! "http://127.0.0.1:3000/v1/torrent/upload" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "torrent_id": 2, +//! "info_hash": "5452869BE36F9F3350CCEE6B4544E7E76CAAADAB" +//! } +//! } +//! ``` +//! +//! **NOTICE**: Info-hashes will be lowercase hex-encoded strings in the future +//! and the [internal database ID could be removed from the response](https://github.com/torrust/torrust-index-backend/discussions/149). +//! +//! **Resource** +//! +//! Refer to the [`TorrustBackend`](crate::models::response::NewTorrentResponse) +//! struct for more information about the response attributes. +//! +//! # Download a torrent +//! +//! `GET /v1/torrent/download/{info_hash}` +//! +//! It downloads a new torrent file from the the index. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/x-bittorrent" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --output mandelbrot_2048x2048_infohash_v1.png.torrent \ +//! "http://127.0.0.1:3000/v1/torrent/download/5452869BE36F9F3350CCEE6B4544E7E76CAAADAB" +//! ``` +//! +//! **Example response** `200` +//! +//! The response is a torrent file `mandelbrot_2048x2048_infohash_v1.png.torrent`. +//! +//! ```text +//! $ imdl torrent show mandelbrot_2048x2048_infohash_v1.png.torrent +//! Name mandelbrot_2048x2048.png +//! Info Hash 1a326de411f96bc15622c62358130f0824f561e1 +//! Torrent Size 492 bytes +//! Content Size 168.17 KiB +//! Private no +//! Tracker udp://localhost:6969/eklijkg8901K2Ol6O6CttT1xlUzO4bFD +//! Announce List Tier 1: udp://localhost:6969/eklijkg8901K2Ol6O6CttT1xlUzO4bFD +//! Tier 2: udp://localhost:6969 +//! Piece Size 16 KiB +//! Piece Count 11 +//! File Count 1 +//! Files mandelbrot_2048x2048.png +//! ``` +//! +//! # Get torrent info +//! +//! `GET /v1/torrents/{info_hash}` +//! +//! It returns the torrent info. +//! +//! **Path parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `info_hash` | `InfoHash` | The info-hash | Yes | `5452869BE36F9F3350CCEE6B4544E7E76CAAADAB` +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request GET \ +//! "http://127.0.0.1:3000/v1/torrent/5452869BE36F9F3350CCEE6B4544E7E76CAAADAB" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "torrent_id": 2, +//! "uploader": "indexadmin", +//! "info_hash": "5452869BE36F9F3350CCEE6B4544E7E76CAAADAB", +//! "title": "MandelbrotSet", +//! "description": "MandelbrotSet image", +//! "category": { +//! "category_id": 5, +//! "name": "software", +//! "num_torrents": 1 +//! }, +//! "upload_date": "2023-05-25 11:33:02", +//! "file_size": 172204, +//! "seeders": 0, +//! "leechers": 0, +//! "files": [ +//! { +//! "path": [ +//! "mandelbrot_2048x2048.png" +//! ], +//! "length": 172204, +//! "md5sum": null +//! } +//! ], +//! "trackers": [ +//! "udp://localhost:6969/eklijkg8901K2Ol6O6CttT1xlUzO4bFD", +//! "udp://localhost:6969" +//! ], +//! "magnet_link": "magnet:?xt=urn:btih:5452869BE36F9F3350CCEE6B4544E7E76CAAADAB&dn=MandelbrotSet&tr=udp%3A%2F%2Flocalhost%3A6969%2Feklijkg8901K2Ol6O6CttT1xlUzO4bFD&tr=udp%3A%2F%2Flocalhost%3A6969" +//! } +//! } +//! ``` +//! +//! **Resource** +//! +//! Refer to the [`TorrentResponse`](crate::models::response::TorrentResponse) +//! struct for more information about the response attributes. +//! +//! # List torrent infos +//! +//! `GET /v1/torrents` +//! +//! It returns the torrent info for multiple torrents +//! +//! **Get parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `search` | `Option` | A text to search | No | `MandelbrotSet` +//! `categories` | `Option` | A coma-separated category list | No | `music,other,movie,software` +//! +//! **Pagination GET parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `page_size` | `Option` | Number of torrents per page | No | `10` +//! `page` | `Option` | Page offset, starting at `0` | No | `music,other,movie,software` +//! +//! Pagination default values can be configured in the server configuration file. +//! +//! ```toml +//! [api] +//! default_torrent_page_size = 10 +//! max_torrent_page_size = 30 +//! ``` +//! +//! **Sorting GET parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `sort` | `Option` | [Sorting](crate::databases::database::Sorting) options | No | `size_DESC` +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request GET \ +//! "http://127.0.0.1:3000/v1/torrents" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "total": 1, +//! "results": [ +//! { +//! "torrent_id": 2, +//! "uploader": "indexadmin", +//! "info_hash": "5452869BE36F9F3350CCEE6B4544E7E76CAAADAB", +//! "title": "MandelbrotSet", +//! "description": "MandelbrotSet image", +//! "category_id": 5, +//! "date_uploaded": "2023-05-25 11:33:02", +//! "file_size": 172204, +//! "seeders": 0, +//! "leechers": 0 +//! } +//! ] +//! } +//! } +//! ``` +//! +//! **Resource** +//! +//! Refer to the [`TorrentsResponse`](crate::models::response::TorrentsResponse) +//! struct for more information about the response attributes. +//! +//! # Update torrent info +//! +//! `POST /v1/torrents/{info_hash}` +//! +//! It updates the torrent info. +//! +//! **Path parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `info_hash` | `InfoHash` | The info-hash | Yes | `5452869BE36F9F3350CCEE6B4544E7E76CAAADAB` +//! +//! **Post parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `title` | `Option` | The torrent title | No | `MandelbrotSet` +//! `description` | `Option` | The torrent description | No | `MandelbrotSet image` +//! +//! +//! Refer to the [`Update`](crate::routes::torrent::Update) +//! struct for more information about the request attributes. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request PUT \ +//! --data '{"title":"MandelbrotSet", "description":"MandelbrotSet image"}' \ +//! "http://127.0.0.1:3000/v1/torrent/5452869BE36F9F3350CCEE6B4544E7E76CAAADAB" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "torrent_id": 2, +//! "uploader": "indexadmin", +//! "info_hash": "5452869BE36F9F3350CCEE6B4544E7E76CAAADAB", +//! "title": "MandelbrotSet", +//! "description": "MandelbrotSet image", +//! "category": { +//! "category_id": 5, +//! "name": "software", +//! "num_torrents": 1 +//! }, +//! "upload_date": "2023-05-25 11:33:02", +//! "file_size": 172204, +//! "seeders": 0, +//! "leechers": 0, +//! "files": [], +//! "trackers": [], +//! "magnet_link": "" +//! } +//! } +//! ``` +//! +//! **NOTICE**: the response is not the same as the `GET /v1/torrents/{info_hash}`. +//! It does not contain the `files`, `trackers` and `magnet_link` attributes. +//! +//! **Resource** +//! +//! Refer to the [`TorrentResponse`](crate::models::response::TorrentResponse) +//! struct for more information about the response attributes. +//! +//! # Delete a torrent +//! +//! `DELETE /v1/torrents/{info_hash}` +//! +//! It deletes a torrent. +//! +//! **Path parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `info_hash` | `InfoHash` | The info-hash | Yes | `5452869BE36F9F3350CCEE6B4544E7E76CAAADAB` +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request DELETE \ +//! "http://127.0.0.1:3000/v1/torrent/5452869BE36F9F3350CCEE6B4544E7E76CAAADAB" +//! ``` +//! +//! **Example response** `200` +//! +//! ```json +//! { +//! "data": { +//! "torrent_id": 2, +//! "info_hash": "5452869BE36F9F3350CCEE6B4544E7E76CAAADAB", +//! } +//! } +//! ``` +//! +//! **Resource** +//! +//! Refer to the [`DeletedTorrentResponse`](crate::models::response::DeletedTorrentResponse) +//! struct for more information about the response attributes. diff --git a/src/web/api/v1/contexts/user/mod.rs b/src/web/api/v1/contexts/user/mod.rs new file mode 100644 index 00000000..c7974a9c --- /dev/null +++ b/src/web/api/v1/contexts/user/mod.rs @@ -0,0 +1,245 @@ +//! API context: `user`. +//! +//! This API context is responsible for handling: +//! +//! - User registration +//! - User authentication +//! - User ban +//! +//! For more information about the API authentication, refer to the [`auth`](crate::web::api::v1::auth) +//! module. +//! +//! # Endpoints +//! +//! Registration: +//! +//! - [Registration](#registration) +//! - [Email verification](#email-verification) +//! +//! Authentication: +//! +//! - [Login](#login) +//! - [Token verification](#token-verification) +//! - [Token renewal](#token-renewal) +//! +//! User ban: +//! +//! - [Ban a user](#ban-a-user) +//! +//! # Registration +//! +//! `POST /v1/user/register` +//! +//! It registers a new user. +//! +//! **Post parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `username` | `String` | The username | Yes | `indexadmin` +//! `email` | `Option` | The user's email | No | `indexadmin@torrust.com` +//! `password` | `String` | The password | Yes | `BenoitMandelbrot1924` +//! `confirm_password` | `String` | Same password again | Yes | `BenoitMandelbrot1924` +//! +//! **NOTICE**: Email could be optional, depending on the configuration. +//! +//! ```toml +//! [auth] +//! email_on_signup = "Optional" +//! min_password_length = 6 +//! max_password_length = 64 +//! ``` +//! +//! Refer to the [`RegistrationForm`](crate::routes::user::RegistrationForm) +//! struct for more information about the registration form. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request POST \ +//! --data '{"username":"indexadmin","email":"indexadmin@torrust.com","password":"BenoitMandelbrot1924","confirm_password":"BenoitMandelbrot1924"}' \ +//! http://127.0.0.1:3000/v1/user/register +//! ``` +//! +//! For more information about the registration process, refer to the [`auth`](crate::web::api::v1::auth) +//! module. +//! +//! # Email verification +//! +//! `GET /v1/user/email/verify/{token}` +//! +//! If email on signup is enabled, the user will receive an email with a +//! verification link. The link will contain a token that can be used to verify +//! the email address. +//! +//! This endpoint will verify the email address and update the user's email +//! verification status. It also shows an text page with the result of the +//! verification. +//! +//! **Example response** `200` +//! +//! ```text +//! Email verified, you can close this page. +//! ``` +//! +//! # Login +//! +//! `POST /v1/user/login` +//! +//! It logs in a user. +//! +//! **Post parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `login` | `String` | The password | Yes | `indexadmin` +//! `password` | `String` | The password | Yes | `BenoitMandelbrot1924` +//! +//! Refer to the [`RegistrationForm`](crate::routes::user::Login) +//! struct for more information about the registration form. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request POST \ +//! --data '{"login":"indexadmin","password":"BenoitMandelbrot1924"}' \ +//! http://127.0.0.1:3000/v1/user/login +//! ``` +//! +//! For more information about the login process, refer to the [`auth`](crate::web::api::v1::auth) +//! module. +//! +//! # Token verification +//! +//! `POST /v1/user/token/verify` +//! +//! It logs in a user. +//! +//! **Post parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `token` | `String` | The token you want to verify | Yes | `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI` +//! +//! Refer to the [`Token`](crate::routes::user::Token) +//! struct for more information about the registration form. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request POST \ +//! --data '{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI"}' \ +//! http://127.0.0.1:3000/v1/user/token/verify +//! ``` +//! +//! **Example response** `200` +//! +//! For a valid token: +//! +//! ```json +//! { +//! "data":"Token is valid." +//! } +//! ``` +//! +//! And for an invalid token: +//! +//! ```json +//! { +//! "data":"Token invalid." +//! } +//! ``` +//! +//! # Token renewal +//! +//! `POST /v1/user/token/verify` +//! +//! It renew a user's token. +//! +//! The token must be valid and not expired. And it's only renewed if it is +//! valid for less than one week. +//! +//! **Post parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `token` | `String` | The current valid token | Yes | `eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI` +//! +//! Refer to the [`Token`](crate::routes::user::Token) +//! struct for more information about the registration form. +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --request POST \ +//! --data '{"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI"}' \ +//! http://127.0.0.1:3000/v1/user/token/renew +//! ``` +//! +//! **Example response** `200` +//! +//! If you try to renew a token that is still valid for more than one week: +//! +//! ```json +//! { +//! "data": { +//! "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI", +//! "username": "indexadmin", +//! "admin": true +//! } +//! } +//! ``` +//! +//! You will get the same token. If a new token is generated, the response will +//! be the same but with the new token. +//! +//! **WARNING**: The token is associated to the user's role. The application does not support +//! changing the role of a user. If you change the user's role manually in the +//! database, the token will still be valid but with the same role. That should +//! only be done for testing purposes. +//! +//! # Ban a user +//! +//! `DELETE /v1/user/ban/{user}` +//! +//! It add a user to the banned user list. +//! +//! Only admin can ban other users. +//! +//! **Path parameters** +//! +//! Name | Type | Description | Required | Example +//! ---|---|---|---|--- +//! `user` | `String` | username | Yes | `indexadmin` +//! +//! **Example request** +//! +//! ```bash +//! curl \ +//! --header "Content-Type: application/json" \ +//! --header "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJfaWQiOjEsInVzZXJuYW1lIjoiaW5kZXhhZG1pbiIsImFkbWluaXN0cmF0b3IiOnRydWV9LCJleHAiOjE2ODYyMTU3ODh9.4k8ty27DiWwOk4WVcYEhIrAndhpXMRWnLZ3i_HlJnvI" \ +//! --request DELETE \ +//! http://127.0.0.1:3000/v1/user/ban/indexadmin +//! ``` +//! +//! **Example response** `200` +//! +//! If you try to renew a token that is still valid for more than one week: +//! +//! ```json +//! { +//! "data": "Banned user: indexadmin" +//! } +//! ``` +//! +//! **WARNING**: The admin can ban themselves. If they do, they will not be able +//! to unban themselves. The only way to unban themselves is to manually remove +//! the user from the banned user list in the database. diff --git a/src/web/api/v1/mod.rs b/src/web/api/v1/mod.rs new file mode 100644 index 00000000..716030ab --- /dev/null +++ b/src/web/api/v1/mod.rs @@ -0,0 +1,8 @@ +//! The torrust Index Backend API version `v1`. +//! +//! The API is organized in contexts. +//! +//! Refer to the [`contexts`](crate::web::api::v1::contexts) module for more +//! information. +pub mod auth; +pub mod contexts; diff --git a/src/web/mod.rs b/src/web/mod.rs new file mode 100644 index 00000000..f837326d --- /dev/null +++ b/src/web/mod.rs @@ -0,0 +1,6 @@ +//! The Torrust Index Backend API. +//! +//! Currently, the API has only one version: `v1`. +//! +//! Refer to the [`v1`](crate::web::api::v1) module for more information. +pub mod api;