From 7ffb44263bd5a400217c9062e8e11011610b0a4a Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 28 May 2024 13:28:13 +0200 Subject: [PATCH] work on single app --- .vscode/settings.json | 1 + Cargo.lock | 60 +- Cargo.toml | 4 +- ffplayout-engine/Cargo.toml | 6 +- ffplayout-engine/src/main.rs | 4 +- ffplayout/Cargo.toml | 60 ++ ffplayout/build.rs | 15 + ffplayout/src/api/auth.rs | 46 ++ ffplayout/src/api/mod.rs | 2 + ffplayout/src/api/routes.rs | 1230 +++++++++++++++++++++++++++++ ffplayout/src/db/handles.rs | 351 ++++++++ ffplayout/src/db/mod.rs | 13 + ffplayout/src/db/models.rs | 123 +++ ffplayout/src/lib.rs | 21 + ffplayout/src/main.rs | 216 +++++ ffplayout/src/player/mod.rs | 0 ffplayout/src/sse/broadcast.rs | 160 ++++ ffplayout/src/sse/mod.rs | 55 ++ ffplayout/src/sse/routes.rs | 82 ++ ffplayout/src/utils/args_parse.rs | 36 + ffplayout/src/utils/channels.rs | 70 ++ ffplayout/src/utils/control.rs | 345 ++++++++ ffplayout/src/utils/errors.rs | 105 +++ ffplayout/src/utils/files.rs | 456 +++++++++++ ffplayout/src/utils/mod.rs | 390 +++++++++ ffplayout/src/utils/playlist.rs | 152 ++++ ffplayout/src/utils/system.rs | 176 +++++ lib/src/utils/controller.rs | 2 +- tests/Cargo.toml | 2 +- tests/src/engine_cmd.rs | 2 +- tests/src/engine_playlist.rs | 2 +- 31 files changed, 4168 insertions(+), 19 deletions(-) create mode 100644 ffplayout/Cargo.toml create mode 100644 ffplayout/build.rs create mode 100644 ffplayout/src/api/auth.rs create mode 100644 ffplayout/src/api/mod.rs create mode 100644 ffplayout/src/api/routes.rs create mode 100644 ffplayout/src/db/handles.rs create mode 100644 ffplayout/src/db/mod.rs create mode 100644 ffplayout/src/db/models.rs create mode 100644 ffplayout/src/lib.rs create mode 100644 ffplayout/src/main.rs create mode 100644 ffplayout/src/player/mod.rs create mode 100644 ffplayout/src/sse/broadcast.rs create mode 100644 ffplayout/src/sse/mod.rs create mode 100644 ffplayout/src/sse/routes.rs create mode 100644 ffplayout/src/utils/args_parse.rs create mode 100644 ffplayout/src/utils/channels.rs create mode 100644 ffplayout/src/utils/control.rs create mode 100644 ffplayout/src/utils/errors.rs create mode 100644 ffplayout/src/utils/files.rs create mode 100644 ffplayout/src/utils/mod.rs create mode 100644 ffplayout/src/utils/playlist.rs create mode 100644 ffplayout/src/utils/system.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index ed2e7ef1..687c1825 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,6 +19,7 @@ }, "cSpell.words": [ "actix", + "ffpengine", "rsplit", "starttls", "tokio", diff --git a/Cargo.lock b/Cargo.lock index f20e0e4b..31e660e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1294,22 +1294,44 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" name = "ffplayout" version = "0.23.0" dependencies = [ + "actix-files", + "actix-multipart", + "actix-web", + "actix-web-grants", + "actix-web-httpauth", + "actix-web-lab", + "actix-web-static-files", + "argon2", "chrono", "clap", - "crossbeam-channel", + "derive_more", + "faccess", "ffplayout-lib", - "futures", - "itertools", - "notify", - "notify-debouncer-full", + "futures-util", + "home", + "jsonwebtoken", + "lazy_static", + "lexical-sort", + "local-ip-address", + "once_cell", + "parking_lot", + "path-clean", "rand", "regex", + "relative-path", "reqwest", + "rpassword", + "sanitize-filename", "serde", "serde_json", "simplelog", - "tiny_http", - "zeromq", + "sqlx", + "static-files", + "sysinfo", + "tokio", + "tokio-stream", + "toml_edit", + "uuid", ] [[package]] @@ -1356,6 +1378,28 @@ dependencies = [ "uuid", ] +[[package]] +name = "ffplayout-engine" +version = "0.23.0" +dependencies = [ + "chrono", + "clap", + "crossbeam-channel", + "ffplayout-lib", + "futures", + "itertools", + "notify", + "notify-debouncer-full", + "rand", + "regex", + "reqwest", + "serde", + "serde_json", + "simplelog", + "tiny_http", + "zeromq", +] + [[package]] name = "ffplayout-lib" version = "0.23.0" @@ -3593,7 +3637,7 @@ version = "0.23.0" dependencies = [ "chrono", "crossbeam-channel", - "ffplayout", + "ffplayout-engine", "ffplayout-lib", "ffprobe", "file-rotate", diff --git a/Cargo.toml b/Cargo.toml index c619d299..a8a56b57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = ["ffplayout-api", "ffplayout-engine", "lib", "tests"] -default-members = ["ffplayout-api", "ffplayout-engine", "tests"] +members = ["ffplayout", "ffplayout-api", "ffplayout-engine", "lib", "tests"] +default-members = ["ffplayout", "ffplayout-api", "ffplayout-engine", "tests"] resolver = "2" [workspace.package] diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml index f96a307a..60f992e2 100644 --- a/ffplayout-engine/Cargo.toml +++ b/ffplayout-engine/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ffplayout" +name = "ffplayout-engine" description = "24/7 playout based on rust and ffmpeg" readme = "README.md" version.workspace = true @@ -8,7 +8,7 @@ authors.workspace = true repository.workspace = true edition.workspace = true -default-run = "ffplayout" +default-run = "ffplayout_engine" [dependencies] ffplayout-lib = { path = "../lib" } @@ -32,7 +32,7 @@ zeromq = { version = "0.3", default-features = false, features = [ ] } [[bin]] -name = "ffplayout" +name = "ffplayout_engine" path = "src/main.rs" # DEBIAN DEB PACKAGE diff --git a/ffplayout-engine/src/main.rs b/ffplayout-engine/src/main.rs index 386194d0..29c8faee 100644 --- a/ffplayout-engine/src/main.rs +++ b/ffplayout-engine/src/main.rs @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use simplelog::*; -use ffplayout::{ +use ffplayout_engine::{ output::{player, write_hls}, rpc::run_server, utils::{arg_parse::get_args, get_config}, @@ -25,7 +25,7 @@ use ffplayout_lib::utils::{ }; #[cfg(debug_assertions)] -use ffplayout::utils::Args; +use ffplayout_engine::utils::Args; #[cfg(debug_assertions)] use ffplayout_lib::utils::{mock_time, time_now}; diff --git a/ffplayout/Cargo.toml b/ffplayout/Cargo.toml new file mode 100644 index 00000000..e48b1ec5 --- /dev/null +++ b/ffplayout/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "ffplayout" +description = "Rest API for ffplayout" +readme = "README.md" +version.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +edition.workspace = true + +[features] +default = ["embed_frontend"] +embed_frontend = [] + +[dependencies] +ffplayout-lib = { path = "../lib" } +actix-files = "0.6" +actix-multipart = "0.6" +actix-web = "4" +actix-web-grants = "4" +actix-web-httpauth = "0.8" +actix-web-lab = "0.20" +actix-web-static-files = "4.0" +argon2 = "0.5" +chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } +clap = { version = "4.3", features = ["derive"] } +derive_more = "0.99" +faccess = "0.2" +futures-util = { version = "0.3", default-features = false, features = ["std"] } +home = "0.5" +jsonwebtoken = "9" +lazy_static = "1.4" +lexical-sort = "0.3" +local-ip-address = "0.6" +once_cell = "1.18" +parking_lot = "0.12" +path-clean = "1.0" +rand = "0.8" +regex = "1" +relative-path = "1.8" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } +rpassword = "7.2" +sanitize-filename = "0.5" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +simplelog = { version = "0.12", features = ["paris"] } +static-files = "0.2" +sysinfo ={ version = "0.30", features = ["linux-netdevs"] } +sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite"] } +tokio = { version = "1.29", features = ["full"] } +tokio-stream = "0.1" +toml_edit = {version ="0.22", features = ["serde"]} +uuid = "1.8" + +[build-dependencies] +static-files = "0.2" + +[[bin]] +name = "ffplayout" +path = "src/main.rs" diff --git a/ffplayout/build.rs b/ffplayout/build.rs new file mode 100644 index 00000000..df50d511 --- /dev/null +++ b/ffplayout/build.rs @@ -0,0 +1,15 @@ +use static_files::NpmBuild; + +fn main() -> std::io::Result<()> { + if !cfg!(debug_assertions) && cfg!(feature = "embed_frontend") { + NpmBuild::new("../ffplayout-frontend") + .install()? + .run("generate")? + .target("../ffplayout-frontend/.output/public") + .change_detection() + .to_resource_dir() + .build() + } else { + Ok(()) + } +} diff --git a/ffplayout/src/api/auth.rs b/ffplayout/src/api/auth.rs new file mode 100644 index 00000000..9c933a68 --- /dev/null +++ b/ffplayout/src/api/auth.rs @@ -0,0 +1,46 @@ +use actix_web::error::ErrorUnauthorized; +use actix_web::Error; +use chrono::{TimeDelta, Utc}; +use jsonwebtoken::{self, DecodingKey, EncodingKey, Header, Validation}; +use serde::{Deserialize, Serialize}; + +use crate::utils::{GlobalSettings, Role}; + +// Token lifetime +const JWT_EXPIRATION_DAYS: i64 = 7; + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct Claims { + pub id: i32, + pub username: String, + pub role: Role, + exp: i64, +} + +impl Claims { + pub fn new(id: i32, username: String, role: Role) -> Self { + Self { + id, + username, + role, + exp: (Utc::now() + TimeDelta::try_days(JWT_EXPIRATION_DAYS).unwrap()).timestamp(), + } + } +} + +/// Create a json web token (JWT) +pub fn create_jwt(claims: Claims) -> Result { + let config = GlobalSettings::global(); + let encoding_key = EncodingKey::from_secret(config.secret.as_bytes()); + jsonwebtoken::encode(&Header::default(), &claims, &encoding_key) + .map_err(|e| ErrorUnauthorized(e.to_string())) +} + +/// Decode a json web token (JWT) +pub async fn decode_jwt(token: &str) -> Result { + let config = GlobalSettings::global(); + let decoding_key = DecodingKey::from_secret(config.secret.as_bytes()); + jsonwebtoken::decode::(token, &decoding_key, &Validation::default()) + .map(|data| data.claims) + .map_err(|e| ErrorUnauthorized(e.to_string())) +} diff --git a/ffplayout/src/api/mod.rs b/ffplayout/src/api/mod.rs new file mode 100644 index 00000000..474f258a --- /dev/null +++ b/ffplayout/src/api/mod.rs @@ -0,0 +1,2 @@ +pub mod auth; +pub mod routes; diff --git a/ffplayout/src/api/routes.rs b/ffplayout/src/api/routes.rs new file mode 100644 index 00000000..3eab3073 --- /dev/null +++ b/ffplayout/src/api/routes.rs @@ -0,0 +1,1230 @@ +/// ### Possible endpoints +/// +/// Run the API thru the systemd service, or like: +/// +/// ```BASH +/// ffpapi -l 127.0.0.1:8787 +/// ``` +/// +/// For all endpoints an (Bearer) authentication is required.\ +/// `{id}` represent the channel id, and at default is 1. +use std::{collections::HashMap, env, path::PathBuf}; + +use actix_files; +use actix_multipart::Multipart; +use actix_web::{ + delete, get, + http::{ + header::{ContentDisposition, DispositionType}, + StatusCode, + }, + patch, post, put, web, HttpRequest, HttpResponse, Responder, +}; +use actix_web_grants::{authorities::AuthDetails, proc_macro::protect}; + +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHash, SaltString}, + Argon2, PasswordHasher, PasswordVerifier, +}; +use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeDelta, TimeZone, Utc}; +use path_clean::PathClean; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use simplelog::*; +use sqlx::{Pool, Sqlite}; +use tokio::{fs, task}; + +use crate::db::{ + handles, + models::{Channel, LoginUser, TextPreset, User}, +}; +use crate::utils::{ + channels::{create_channel, delete_channel}, + control::{control_service, control_state, media_info, send_message, ControlParams, Process}, + errors::ServiceError, + files::{ + browser, create_directory, norm_abs_path, remove_file_or_folder, rename_file, upload, + MoveObject, PathObject, + }, + naive_date_time_from_str, + playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist}, + playout_config, public_path, read_log_file, read_playout_config, system, Role, +}; +use crate::{ + api::auth::{create_jwt, Claims}, + utils::control::ProcessControl, +}; +use ffplayout_lib::{ + utils::{ + get_date_range, import::import_file, sec_to_time, time_to_sec, JsonPlaylist, PlayoutConfig, + Template, + }, + vec_strings, +}; + +#[derive(Serialize)] +struct UserObj { + message: String, + user: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DateObj { + #[serde(default)] + date: String, +} + +#[derive(Debug, Deserialize, Serialize)] +struct FileObj { + #[serde(default)] + path: PathBuf, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct PathsObj { + #[serde(default)] + paths: Option>, + template: Option