save dottorrent file to disk, added route to download dottorrent file

This commit is contained in:
FrenchGithubUser
2025-04-13 14:56:40 +02:00
parent 828a04d66b
commit eb193670dc
9 changed files with 111 additions and 13 deletions

2
.env
View File

@@ -22,6 +22,8 @@ DATABASE_URL=postgresql://${POSTGRESQL_USER}:${POSTGRESQL_PASSWORD}@${POSTGRESQL
# arcadia config
ARCADIA_OPEN_SIGNUPS=true
ARCADIA_DOTTORRENT_FILES_PATH=/absolute/path/to/dottorrent_files
ARCADIA_FRONTEND_URL=https://site.com
# Required for TMDB access, must create a new account with themoviedb.org
# TMDB_API_TOKEN="your token"

View File

@@ -75,3 +75,5 @@ You can also hop on the [discord server](https://discord.gg/amYWVk7pS3) to chat
Arcadia-index has a [board](https://github.com/orgs/Arcadia-Solutions/projects/1) to track the existing issues and features that need to be worked on. Feel free to claim one that isn't claimed yet before starting to work on it.
To claim a github issue, simply leave a comment on it saying that you are working on it.
You can also search for `TODO` in the code and pick one of those tasks. If you decide to do this, please open an issue first and clam it before working on the task.

View File

@@ -24,6 +24,7 @@ rand = "0.9.0"
bip_metainfo = "0.12.0"
serde_bytes = "0.11"
actix-multipart = "0.7.2"
actix-files = "0.6.6"
reqwest = { version = "0.12", features = ["json"] }
actix-cors = "0.7.1"
actix-http = "3.10.0"

View File

@@ -1,7 +1,10 @@
use utoipa::OpenApi;
use crate::{
handlers::{artist_handler::GetArtistPublicationsQuery, auth_handler::RegisterQuery},
handlers::{
artist_handler::GetArtistPublicationsQuery, auth_handler::RegisterQuery,
torrent_handler::DownloadTorrentQuery,
},
models::user::{Login, Register},
};
@@ -12,7 +15,14 @@ use crate::{
crate::handlers::auth_handler::register,
crate::handlers::auth_handler::login,
crate::handlers::artist_handler::get_artist_publications,
crate::handlers::torrent_handler::download_dottorrent_file,
),
components(schemas(Register, RegisterQuery, Login, GetArtistPublicationsQuery),)
components(schemas(
Register,
RegisterQuery,
Login,
GetArtistPublicationsQuery,
DownloadTorrentQuery
),)
)]
pub struct ApiDoc;

View File

@@ -1,8 +1,11 @@
use actix_files::NamedFile;
use actix_multipart::form::MultipartForm;
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use utoipa::{IntoParams, ToSchema};
use crate::{
Arcadia, Result,
Arcadia, Error, Result,
models::{torrent::UploadedTorrent, user::User},
repositories::torrent_repository::create_torrent,
};
@@ -14,7 +17,50 @@ pub async fn upload_torrent(
) -> Result<HttpResponse> {
// TODO : check if user can upload
let torrent = create_torrent(&arc.pool, &form, &current_user).await?;
let torrent = create_torrent(
&arc.pool,
&form,
&current_user,
&arc.frontend_url,
&arc.dottorrent_files_path,
)
.await?;
Ok(HttpResponse::Created().json(torrent))
}
#[derive(Debug, Deserialize, IntoParams, ToSchema)]
pub struct DownloadTorrentQuery {
id: i64,
}
#[utoipa::path(
get,
path = "/api/torrent",
params (DownloadTorrentQuery),
responses(
(status = 200, description = "Successfully downloaded the torrent file"),
)
)]
pub async fn download_dottorrent_file(
query: web::Query<DownloadTorrentQuery>,
arc: web::Data<Arcadia>,
current_user: User,
) -> Result<NamedFile> {
let file_path = &arc
.dottorrent_files_path
.join(query.id.to_string() + ".torrent");
// should never happen as query.id is an int, but we never know...
if !file_path.starts_with(&arc.dottorrent_files_path) {
println!(
"User(username: {}, id: {}) attempted path traversal: {:#?}",
current_user.username,
current_user.id,
file_path.to_str()
);
return Err(Error::DottorrentFileNotFound);
}
actix_files::NamedFile::open(&file_path).map_err(|_| Error::DottorrentFileNotFound)
}

View File

@@ -1,3 +1,6 @@
use std::path::PathBuf;
use reqwest::Url;
pub mod api_doc;
pub mod handlers;
pub mod models;
@@ -13,6 +16,8 @@ pub enum OpenSignups {
pub struct Arcadia {
pub pool: sqlx::PgPool,
pub open_signups: OpenSignups,
pub dottorrent_files_path: PathBuf,
pub frontend_url: Url,
}
impl Arcadia {
@@ -105,6 +110,12 @@ pub enum Error {
#[error("not enough upload to place this bounty")]
InsufficientUploadForBounty,
#[error("dottorrent file not found")]
DottorrentFileNotFound,
#[error("could not save torrent file in path: '{0}'\n'{1}'")]
CouldNotSaveTorrentFile(String, String),
#[error("unexpected third party response")]
UnexpectedThirdPartyResponse(#[from] reqwest::Error),
}

View File

@@ -6,9 +6,10 @@ mod routes;
use actix_cors::Cors;
use actix_web::{App, HttpServer, middleware, web::Data};
use dotenv;
use reqwest::Url;
use routes::init;
use sqlx::postgres::PgPoolOptions;
use std::env;
use std::{env, path::PathBuf, str::FromStr};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
@@ -37,6 +38,12 @@ async fn main() -> std::io::Result<()> {
OpenSignups::Disabled
};
let dottorrent_files_path = env::var("ARCADIA_DOTTORRENT_FILES_PATH")
.expect("ARCADIA_DOTTORRENT_FILES_PATH env var is not set");
let frontend_url =
env::var("ARCADIA_FRONTEND_URL").expect("ARCADIA_FRONTEND_URL env var is not set");
HttpServer::new(move || {
let cors = Cors::permissive();
App::new()
@@ -44,7 +51,10 @@ async fn main() -> std::io::Result<()> {
.wrap(cors)
.app_data(Data::new(Arcadia {
pool: pool.clone(),
open_signups,
open_signups: open_signups,
dottorrent_files_path: PathBuf::from(&dottorrent_files_path),
frontend_url: Url::from_str(&frontend_url)
.expect("ARCADIA_FRONTEND_URL env var malformed"),
}))
.configure(init) // Initialize routes
.service(

View File

@@ -5,10 +5,12 @@ use crate::{
user::User,
},
};
use bip_metainfo::Metainfo;
use reqwest::Url;
use serde_json::json;
use sqlx::PgPool;
use std::str::FromStr;
use std::{fs, path::PathBuf, str::FromStr};
use super::notification_repository::notify_users;
@@ -22,6 +24,8 @@ pub async fn create_torrent(
pool: &PgPool,
torrent_form: &UploadedTorrent,
current_user: &User,
frontend_url: &Url,
dottorrent_files_path: &PathBuf,
) -> Result<Torrent> {
let create_torrent_query = r#"
INSERT INTO torrents (
@@ -131,8 +135,6 @@ pub async fn create_torrent(
.await
.map_err(Error::CouldNotCreateTorrent)?;
// TODO: edit the torrent file with proper flags, remove announce url and store it to the disk
let title_group_info = sqlx::query_as!(
LiteTitleGroupInfo,
r#"
@@ -146,6 +148,17 @@ pub async fn create_torrent(
.fetch_one(pool)
.await?;
//TODO: edit torrent file : remove announce url, add comment with torrent url, etc.
let output_path = format!(
"{}",
dottorrent_files_path
.join(format!("{}{}", uploaded_torrent.id, ".torrent"))
.to_str()
.unwrap()
);
fs::write(&output_path, &torrent_form.torrent_file.data)
.map_err(|error| Error::CouldNotSaveTorrentFile(output_path, error.to_string()))?;
let _ = notify_users(
pool,
&"torrent_uploaded",

View File

@@ -6,13 +6,15 @@ use crate::handlers::{
edition_group_handler::add_edition_group,
invitation_handler::send_invitation,
master_group_handler::add_master_group,
scrapers::open_library::get_open_library_data,
scrapers::tmdb::{get_tmdb_movie_data, get_tmdb_tv_data},
scrapers::{
open_library::get_open_library_data,
tmdb::{get_tmdb_movie_data, get_tmdb_tv_data},
},
series_handler::{add_series, get_series},
subscriptions_handler::{add_subscription, remove_subscription},
title_group_comment_handler::add_title_group_comment,
title_group_handler::{add_title_group, get_lite_title_group_info, get_title_group},
torrent_handler::upload_torrent,
torrent_handler::{download_dottorrent_file, upload_torrent},
torrent_request_handler::add_torrent_request,
torrent_request_vote_handler::add_torrent_request_vote,
user_handler::{get_me, get_user},
@@ -38,6 +40,7 @@ pub fn init(cfg: &mut web::ServiceConfig) {
)
.route("/edition-group", web::post().to(add_edition_group))
.route("/torrent", web::post().to(upload_torrent))
.route("/torrent", web::get().to(download_dottorrent_file))
.route("/artist", web::post().to(add_artist))
.route("/artist", web::get().to(get_artist_publications))
.route(