Merge pull request #239 from jb-alvarado/master

This commit is contained in:
jb-alvarado 2022-11-20 20:23:18 +01:00 committed by GitHub
commit dc3a8b68e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 638 additions and 320 deletions

132
Cargo.lock generated
View File

@ -217,7 +217,7 @@ dependencies = [
"serde_urlencoded",
"smallvec",
"socket2",
"time 0.3.16",
"time 0.3.17",
"url",
]
@ -341,22 +341,22 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28"
dependencies = [
"concurrent-queue",
"concurrent-queue 1.2.4",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
dependencies = [
"async-lock",
"async-task",
"concurrent-queue",
"concurrent-queue 2.0.0",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
@ -383,7 +383,7 @@ checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7"
dependencies = [
"async-lock",
"autocfg",
"concurrent-queue",
"concurrent-queue 1.2.4",
"futures-lite",
"libc",
"log",
@ -451,9 +451,9 @@ dependencies = [
[[package]]
name = "asynchronous-codec"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690"
checksum = "06a0daa378f5fd10634e44b0a29b2a87b890657658e072a30d6f26e57ddee182"
dependencies = [
"bytes",
"futures-sink",
@ -514,9 +514,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake2"
version = "0.10.4"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e"
dependencies = [
"digest",
]
@ -594,9 +594,9 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "bytestring"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b6a75fd3048808ef06af5cd79712be8111960adaf89d90250974b38fc3928a"
checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1"
dependencies = [
"bytes",
]
@ -609,9 +609,9 @@ checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "cc"
version = "1.0.74"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
dependencies = [
"jobserver",
]
@ -630,9 +630,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
@ -701,6 +701,15 @@ dependencies = [
"cache-padded",
]
[[package]]
name = "concurrent-queue"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "convert_case"
version = "0.4.0"
@ -714,7 +723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
dependencies = [
"percent-encoding",
"time 0.3.16",
"time 0.3.17",
"version_check",
]
@ -818,9 +827,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.80"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a"
checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
dependencies = [
"cc",
"cxxbridge-flags",
@ -830,9 +839,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.80"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827"
checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
dependencies = [
"cc",
"codespan-reporting",
@ -845,15 +854,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.80"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a"
checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
[[package]]
name = "cxxbridge-macro"
version = "1.0.80"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7"
checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
dependencies = [
"proc-macro2",
"quote",
@ -888,9 +897,9 @@ dependencies = [
[[package]]
name = "digest"
version = "0.10.5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
@ -962,7 +971,7 @@ dependencies = [
[[package]]
name = "ffplayout"
version = "0.16.3"
version = "0.16.4"
dependencies = [
"chrono",
"clap",
@ -982,7 +991,7 @@ dependencies = [
[[package]]
name = "ffplayout-api"
version = "0.7.1"
version = "0.8.0"
dependencies = [
"actix-files",
"actix-multipart",
@ -999,6 +1008,7 @@ dependencies = [
"jsonwebtoken",
"once_cell",
"rand",
"regex",
"relative-path",
"reqwest",
"rpassword",
@ -1012,7 +1022,7 @@ dependencies = [
[[package]]
name = "ffplayout-lib"
version = "0.16.3"
version = "0.16.4"
dependencies = [
"chrono",
"crossbeam-channel",
@ -1031,7 +1041,7 @@ dependencies = [
"serde_yaml",
"shlex",
"simplelog",
"time 0.3.16",
"time 0.3.17",
"walkdir",
"winapi 0.3.9",
]
@ -1430,9 +1440,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.22"
version = "0.14.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abfba89e19b959ca163c7752ba59d737c1ceea53a5d31a149c805446fc958064"
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
dependencies = [
"bytes",
"futures-channel",
@ -1512,9 +1522,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.1"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
@ -1560,9 +1570,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.5.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
[[package]]
name = "itertools"
@ -2062,9 +2072,9 @@ dependencies = [
[[package]]
name = "os_str_bytes"
version = "6.3.1"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9"
checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
[[package]]
name = "paris"
@ -2212,9 +2222,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
@ -2305,9 +2315,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
@ -2316,9 +2326,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.27"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "relative-path"
@ -2337,9 +2347,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.11.12"
version = "0.11.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
dependencies = [
"base64",
"bytes",
@ -2506,9 +2516,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.87"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7"
dependencies = [
"itoa",
"ryu",
@ -2585,7 +2595,7 @@ dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time 0.3.16",
"time 0.3.17",
]
[[package]]
@ -2597,7 +2607,7 @@ dependencies = [
"log",
"paris",
"termcolor",
"time 0.3.16",
"time 0.3.17",
]
[[package]]
@ -2813,7 +2823,7 @@ dependencies = [
"serde_yaml",
"shlex",
"simplelog",
"time 0.3.16",
"time 0.3.17",
"walkdir",
]
@ -2856,9 +2866,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca"
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [
"itoa",
"libc",
@ -2876,9 +2886,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "time-macros"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b"
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
dependencies = [
"time-core",
]
@ -2900,9 +2910,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.21.2"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3"
dependencies = [
"autocfg",
"bytes",
@ -3088,9 +3098,9 @@ dependencies = [
[[package]]
name = "uuid"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83"
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
dependencies = [
"getrandom",
]

View File

@ -20,7 +20,7 @@ Check the [releases](https://github.com/ffplayout/ffplayout/releases/latest) for
- send emails with error message
- overlay a logo
- overlay text, controllable through [ffplayout-frontend](https://github.com/ffplayout/ffplayout-frontend) (needs ffmpeg with libzmq and enabled JSON RPC server)
- EBU R128 loudness normalization (single pass)
- EBU R128 loudness normalization (single pass) (experimental *)
- loop playlist infinitely
- [remote source](/docs/remote_source.md)
- trim and fade the last clip, to get full 24 hours

View File

@ -284,7 +284,7 @@ curl -X DELETE http://127.0.0.1:8787/api/playlist/1/2022-06-20
**Read Log Life**
```BASH
curl -X Get http://127.0.0.1:8787/api/log/1
curl -X GET http://127.0.0.1:8787/api/log/1
-H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>'
```
@ -321,7 +321,7 @@ curl -X POST http://127.0.0.1:8787/api/file/1/remove/ -H 'Content-Type: applicat
**Upload File**
```BASH
curl -X POST http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer <TOKEN>'
curl -X PUT http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer <TOKEN>'
-F "file=@file.mp4"
```
@ -331,6 +331,30 @@ Import text/m3u file and convert it to a playlist
lines with leading "#" will be ignore
```BASH
curl -X POST http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer <TOKEN>'
curl -X PUT http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer <TOKEN>'
-F "file=@list.m3u"
```
**Program info**
Get program infos about given date, or current day
Examples:
* get program from current day
```BASH
curl -X GET http://127.0.0.1:8787/api/program/1/ -H 'Authorization: Bearer <TOKEN>'
```
* get a program range between two dates
```BASH
curl -X GET http://127.0.0.1:8787/api/program/1/?start_after=2022-11-13T12:00:00&start_before=2022-11-20T11:59:59 \
-H 'Authorization: Bearer <TOKEN>'
```
* get program from give day
```BASH
curl -X GET http://127.0.0.1:8787/api/program/1/?start_after=2022-11-13T10:00:00 \
-H 'Authorization: Bearer <TOKEN>'
```

View File

@ -4,7 +4,7 @@ description = "Rest API for ffplayout"
license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md"
version = "0.7.1"
version = "0.8.0"
edition = "2021"
[dependencies]
@ -23,6 +23,7 @@ futures-util = { version = "0.3", default-features = false, features = ["std"] }
jsonwebtoken = "8"
once_cell = "1.10"
rand = "0.8"
regex = "1"
relative-path = "1.6"
reqwest = { version = "0.11", features = ["blocking", "json"] }
rpassword = "6.0"

View File

@ -17,8 +17,11 @@ use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, SaltString},
Argon2, PasswordHasher, PasswordVerifier,
};
use chrono::{DateTime, Datelike, Duration, Local, NaiveDateTime, TimeZone, Utc};
use regex::Regex;
use serde::{Deserialize, Serialize};
use simplelog::*;
use sqlx::{Pool, Sqlite};
use crate::auth::{create_jwt, Claims};
use crate::db::{
@ -33,10 +36,16 @@ use crate::utils::{
browser, create_directory, 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, read_log_file, read_playout_config, Role,
};
use ffplayout_lib::utils::{import::import_file, JsonPlaylist, PlayoutConfig};
use ffplayout_lib::{
utils::{
get_date_range, import::import_file, sec_to_time, time_to_sec, JsonPlaylist, PlayoutConfig,
},
vec_strings,
};
#[derive(Serialize)]
struct ResponseObj<T> {
@ -71,6 +80,42 @@ pub struct ImportObj {
date: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ProgramObj {
#[serde(default = "time_after", deserialize_with = "naive_date_time_from_str")]
start_after: NaiveDateTime,
#[serde(default = "time_before", deserialize_with = "naive_date_time_from_str")]
start_before: NaiveDateTime,
}
fn time_after() -> NaiveDateTime {
let today = Utc::now();
chrono::Local
.with_ymd_and_hms(today.year(), today.month(), today.day(), 0, 0, 0)
.unwrap()
.naive_local()
}
fn time_before() -> NaiveDateTime {
let today = Utc::now();
chrono::Local
.with_ymd_and_hms(today.year(), today.month(), today.day(), 23, 59, 59)
.unwrap()
.naive_local()
}
#[derive(Debug, Serialize)]
struct ProgramItem {
source: String,
start: String,
r#in: f64,
out: f64,
duration: f64,
category: String,
}
/// #### User Handling
///
/// **Login**
@ -90,8 +135,9 @@ pub struct ImportObj {
/// }
/// ```
#[post("/auth/login/")]
pub async fn login(credentials: web::Json<User>) -> impl Responder {
match handles::select_login(&credentials.username).await {
pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>) -> impl Responder {
let conn = pool.into_inner();
match handles::select_login(&conn, &credentials.username).await {
Ok(mut user) => {
let pass = user.password.clone();
let hash = PasswordHash::new(&pass).unwrap();
@ -102,7 +148,7 @@ pub async fn login(credentials: web::Json<User>) -> impl Responder {
.verify_password(credentials.password.as_bytes(), &hash)
.is_ok()
{
let role = handles::select_role(&user.role_id.unwrap_or_default())
let role = handles::select_role(&conn, &user.role_id.unwrap_or_default())
.await
.unwrap_or_else(|_| "guest".to_string());
let claims = Claims::new(user.id, user.username.clone(), role.clone());
@ -152,8 +198,11 @@ pub async fn login(credentials: web::Json<User>) -> impl Responder {
/// ```
#[get("/user")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_user(user: web::ReqData<LoginUser>) -> Result<impl Responder, ServiceError> {
match handles::select_user(&user.username).await {
async fn get_user(
pool: web::Data<Pool<Sqlite>>,
user: web::ReqData<LoginUser>,
) -> Result<impl Responder, ServiceError> {
match handles::select_user(&pool.into_inner(), &user.username).await {
Ok(user) => Ok(web::Json(user)),
Err(e) => {
error!("{e}");
@ -171,6 +220,7 @@ async fn get_user(user: web::ReqData<LoginUser>) -> Result<impl Responder, Servi
#[put("/user/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn update_user(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
user: web::ReqData<LoginUser>,
data: web::Json<User>,
@ -195,7 +245,10 @@ async fn update_user(
fields.push_str(format!("password = '{}', salt = '{salt}'", password_hash).as_str());
}
if handles::update_user(user.id, fields).await.is_ok() {
if handles::update_user(&pool.into_inner(), user.id, fields)
.await
.is_ok()
{
return Ok("Update Success");
};
@ -214,8 +267,11 @@ async fn update_user(
/// ```
#[post("/user/")]
#[has_any_role("Role::Admin", type = "Role")]
async fn add_user(data: web::Json<User>) -> Result<impl Responder, ServiceError> {
match handles::insert_user(data.into_inner()).await {
async fn add_user(
pool: web::Data<Pool<Sqlite>>,
data: web::Json<User>,
) -> Result<impl Responder, ServiceError> {
match handles::insert_user(&pool.into_inner(), data.into_inner()).await {
Ok(_) => Ok("Add User Success"),
Err(e) => {
error!("{e}");
@ -247,8 +303,11 @@ async fn add_user(data: web::Json<User>) -> Result<impl Responder, ServiceError>
/// ```
#[get("/channel/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_channel(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_channel(&id).await {
async fn get_channel(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await {
return Ok(web::Json(channel));
}
@ -262,8 +321,8 @@ async fn get_channel(id: web::Path<i32>) -> Result<impl Responder, ServiceError>
/// ```
#[get("/channels")]
#[has_any_role("Role::Admin", type = "Role")]
async fn get_all_channels() -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_all_channels().await {
async fn get_all_channels(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await {
return Ok(web::Json(channel));
}
@ -281,10 +340,11 @@ async fn get_all_channels() -> Result<impl Responder, ServiceError> {
#[patch("/channel/{id}")]
#[has_any_role("Role::Admin", type = "Role")]
async fn patch_channel(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<Channel>,
) -> Result<impl Responder, ServiceError> {
if handles::update_channel(*id, data.into_inner())
if handles::update_channel(&pool.into_inner(), *id, data.into_inner())
.await
.is_ok()
{
@ -305,8 +365,11 @@ async fn patch_channel(
/// ```
#[post("/channel/")]
#[has_any_role("Role::Admin", type = "Role")]
async fn add_channel(data: web::Json<Channel>) -> Result<impl Responder, ServiceError> {
match create_channel(data.into_inner()).await {
async fn add_channel(
pool: web::Data<Pool<Sqlite>>,
data: web::Json<Channel>,
) -> Result<impl Responder, ServiceError> {
match create_channel(&pool.into_inner(), data.into_inner()).await {
Ok(c) => Ok(web::Json(c)),
Err(e) => Err(e),
}
@ -319,8 +382,11 @@ async fn add_channel(data: web::Json<Channel>) -> Result<impl Responder, Service
/// ```
#[delete("/channel/{id}")]
#[has_any_role("Role::Admin", type = "Role")]
async fn remove_channel(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
if delete_channel(*id).await.is_ok() {
async fn remove_channel(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
if delete_channel(&pool.into_inner(), *id).await.is_ok() {
return Ok("Delete Channel Success");
}
@ -339,10 +405,11 @@ async fn remove_channel(id: web::Path<i32>) -> Result<impl Responder, ServiceErr
#[get("/playout/config/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_playout_config(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
_details: AuthDetails<Role>,
) -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_channel(&id).await {
if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await {
if let Ok(config) = read_playout_config(&channel.config_path) {
return Ok(web::Json(config));
}
@ -360,10 +427,11 @@ async fn get_playout_config(
#[put("/playout/config/{id}")]
#[has_any_role("Role::Admin", type = "Role")]
async fn update_playout_config(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<PlayoutConfig>,
) -> Result<impl Responder, ServiceError> {
if let Ok(channel) = handles::select_channel(&id).await {
if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await {
if let Ok(f) = std::fs::OpenOptions::new()
.write(true)
.truncate(true)
@ -392,8 +460,11 @@ async fn update_playout_config(
/// ```
#[get("/presets/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_presets(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
if let Ok(presets) = handles::select_presets(*id).await {
async fn get_presets(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
if let Ok(presets) = handles::select_presets(&pool.into_inner(), *id).await {
return Ok(web::Json(presets));
}
@ -411,10 +482,14 @@ async fn get_presets(id: web::Path<i32>) -> Result<impl Responder, ServiceError>
#[put("/presets/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn update_preset(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<TextPreset>,
) -> Result<impl Responder, ServiceError> {
if handles::update_preset(&id, data.into_inner()).await.is_ok() {
if handles::update_preset(&pool.into_inner(), &id, data.into_inner())
.await
.is_ok()
{
return Ok("Update Success");
}
@ -431,8 +506,14 @@ async fn update_preset(
/// ```
#[post("/presets/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn add_preset(data: web::Json<TextPreset>) -> Result<impl Responder, ServiceError> {
if handles::insert_preset(data.into_inner()).await.is_ok() {
async fn add_preset(
pool: web::Data<Pool<Sqlite>>,
data: web::Json<TextPreset>,
) -> Result<impl Responder, ServiceError> {
if handles::insert_preset(&pool.into_inner(), data.into_inner())
.await
.is_ok()
{
return Ok("Add preset Success");
}
@ -447,8 +528,14 @@ async fn add_preset(data: web::Json<TextPreset>) -> Result<impl Responder, Servi
/// ```
#[delete("/presets/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn delete_preset(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
if handles::delete_preset(&id).await.is_ok() {
async fn delete_preset(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
if handles::delete_preset(&pool.into_inner(), &id)
.await
.is_ok()
{
return Ok("Delete preset Success");
}
@ -475,10 +562,11 @@ async fn delete_preset(id: web::Path<i32>) -> Result<impl Responder, ServiceErro
#[post("/control/{id}/text/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn send_text_message(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<HashMap<String, String>>,
) -> Result<impl Responder, ServiceError> {
match send_message(*id, data.into_inner()).await {
match send_message(&pool.into_inner(), *id, data.into_inner()).await {
Ok(res) => Ok(res.text().await.unwrap_or_else(|_| "Success".into())),
Err(e) => Err(e),
}
@ -497,10 +585,11 @@ pub async fn send_text_message(
#[post("/control/{id}/playout/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn control_playout(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
control: web::Json<Process>,
) -> Result<impl Responder, ServiceError> {
match control_state(*id, control.command.clone()).await {
match control_state(&pool.into_inner(), *id, control.command.clone()).await {
Ok(res) => Ok(res.text().await.unwrap_or_else(|_| "Success".into())),
Err(e) => Err(e),
}
@ -538,8 +627,11 @@ pub async fn control_playout(
/// ```
#[get("/control/{id}/media/current")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn media_current(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
match media_info(*id, "current".into()).await {
pub async fn media_current(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
match media_info(&pool.into_inner(), *id, "current".into()).await {
Ok(res) => Ok(res.text().await.unwrap_or_else(|_| "Success".into())),
Err(e) => Err(e),
}
@ -552,8 +644,11 @@ pub async fn media_current(id: web::Path<i32>) -> Result<impl Responder, Service
/// ```
#[get("/control/{id}/media/next")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn media_next(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
match media_info(*id, "next".into()).await {
pub async fn media_next(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
match media_info(&pool.into_inner(), *id, "next".into()).await {
Ok(res) => Ok(res.text().await.unwrap_or_else(|_| "Success".into())),
Err(e) => Err(e),
}
@ -567,8 +662,11 @@ pub async fn media_next(id: web::Path<i32>) -> Result<impl Responder, ServiceErr
/// ```
#[get("/control/{id}/media/last")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn media_last(id: web::Path<i32>) -> Result<impl Responder, ServiceError> {
match media_info(*id, "last".into()).await {
pub async fn media_last(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
) -> Result<impl Responder, ServiceError> {
match media_info(&pool.into_inner(), *id, "last".into()).await {
Ok(res) => Ok(res.text().await.unwrap_or_else(|_| "Success".into())),
Err(e) => Err(e),
}
@ -590,10 +688,11 @@ pub async fn media_last(id: web::Path<i32>) -> Result<impl Responder, ServiceErr
#[post("/control/{id}/process/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn process_control(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
proc: web::Json<Process>,
) -> Result<impl Responder, ServiceError> {
control_service(*id, &proc.command).await
control_service(&pool.into_inner(), *id, &proc.command).await
}
/// #### ffplayout Playlist Operations
@ -607,10 +706,11 @@ pub async fn process_control(
#[get("/playlist/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn get_playlist(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
obj: web::Query<DateObj>,
) -> Result<impl Responder, ServiceError> {
match read_playlist(*id, obj.date.clone()).await {
match read_playlist(&pool.into_inner(), *id, obj.date.clone()).await {
Ok(playlist) => Ok(web::Json(playlist)),
Err(e) => Err(e),
}
@ -626,10 +726,11 @@ pub async fn get_playlist(
#[post("/playlist/{id}/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn save_playlist(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<JsonPlaylist>,
) -> Result<impl Responder, ServiceError> {
match write_playlist(*id, data.into_inner()).await {
match write_playlist(&pool.into_inner(), *id, data.into_inner()).await {
Ok(res) => Ok(res),
Err(e) => Err(e),
}
@ -646,9 +747,10 @@ pub async fn save_playlist(
#[get("/playlist/{id}/generate/{date}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn gen_playlist(
pool: web::Data<Pool<Sqlite>>,
params: web::Path<(i32, String)>,
) -> Result<impl Responder, ServiceError> {
match generate_playlist(params.0, params.1.clone()).await {
match generate_playlist(&pool.into_inner(), params.0, params.1.clone()).await {
Ok(playlist) => Ok(web::Json(playlist)),
Err(e) => Err(e),
}
@ -663,9 +765,10 @@ pub async fn gen_playlist(
#[delete("/playlist/{id}/{date}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn del_playlist(
pool: web::Data<Pool<Sqlite>>,
params: web::Path<(i32, String)>,
) -> Result<impl Responder, ServiceError> {
match delete_playlist(params.0, &params.1).await {
match delete_playlist(&pool.into_inner(), params.0, &params.1).await {
Ok(_) => Ok(format!("Delete playlist from {} success!", params.1)),
Err(e) => Err(e),
}
@ -676,16 +779,17 @@ pub async fn del_playlist(
/// **Read Log Life**
///
/// ```BASH
/// curl -X Get http://127.0.0.1:8787/api/log/1
/// curl -X GET http://127.0.0.1:8787/api/log/1
/// -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>'
/// ```
#[get("/log/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn get_log(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
log: web::Query<DateObj>,
) -> Result<impl Responder, ServiceError> {
read_log_file(&id, &log.date).await
read_log_file(&pool.into_inner(), &id, &log.date).await
}
/// ### File Operations
@ -699,10 +803,11 @@ pub async fn get_log(
#[post("/file/{id}/browse/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn file_browser(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<PathObject>,
) -> Result<impl Responder, ServiceError> {
match browser(*id, &data.into_inner()).await {
match browser(&pool.into_inner(), *id, &data.into_inner()).await {
Ok(obj) => Ok(web::Json(obj)),
Err(e) => Err(e),
}
@ -717,10 +822,11 @@ pub async fn file_browser(
#[post("/file/{id}/create-folder/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn add_dir(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<PathObject>,
) -> Result<HttpResponse, ServiceError> {
create_directory(*id, &data.into_inner()).await
create_directory(&pool.into_inner(), *id, &data.into_inner()).await
}
/// **Rename File**
@ -732,10 +838,11 @@ pub async fn add_dir(
#[post("/file/{id}/rename/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn move_rename(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<MoveObject>,
) -> Result<impl Responder, ServiceError> {
match rename_file(*id, &data.into_inner()).await {
match rename_file(&pool.into_inner(), *id, &data.into_inner()).await {
Ok(obj) => Ok(web::Json(obj)),
Err(e) => Err(e),
}
@ -750,10 +857,11 @@ pub async fn move_rename(
#[post("/file/{id}/remove/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
pub async fn remove(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
data: web::Json<PathObject>,
) -> Result<impl Responder, ServiceError> {
match remove_file_or_folder(*id, &data.into_inner().source).await {
match remove_file_or_folder(&pool.into_inner(), *id, &data.into_inner().source).await {
Ok(obj) => Ok(web::Json(obj)),
Err(e) => Err(e),
}
@ -762,17 +870,18 @@ pub async fn remove(
/// **Upload File**
///
/// ```BASH
/// curl -X POST http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer <TOKEN>'
/// curl -X PUT http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer <TOKEN>'
/// -F "file=@file.mp4"
/// ```
#[put("/file/{id}/upload/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn save_file(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
payload: Multipart,
obj: web::Query<FileObj>,
) -> Result<HttpResponse, ServiceError> {
upload(*id, payload, &obj.path, false).await
upload(&pool.into_inner(), *id, payload, &obj.path, false).await
}
/// **Import playlist**
@ -781,25 +890,126 @@ async fn save_file(
/// lines with leading "#" will be ignore
///
/// ```BASH
/// curl -X POST http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer <TOKEN>'
/// curl -X PUT http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer <TOKEN>'
/// -F "file=@list.m3u"
/// ```
#[put("/file/{id}/import/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn import_playlist(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
payload: Multipart,
obj: web::Query<ImportObj>,
) -> Result<HttpResponse, ServiceError> {
let file = Path::new(&obj.file).file_name().unwrap_or_default();
let path = env::temp_dir().join(file).to_string_lossy().to_string();
let (config, _) = playout_config(&id).await?;
let channel = handles::select_channel(&id).await?;
let (config, _) = playout_config(&pool.clone().into_inner(), &id).await?;
let channel = handles::select_channel(&pool.clone().into_inner(), &id).await?;
upload(*id, payload, &path, true).await?;
upload(&pool.into_inner(), *id, payload, &path, true).await?;
import_file(&config, &obj.date, Some(channel.name), &path)?;
fs::remove_file(path)?;
Ok(HttpResponse::Ok().into())
}
/// **Program info**
///
/// Get program infos about given date, or current day
///
/// Examples:
///
/// * get program from current day
/// ```BASH
/// curl -X GET http://127.0.0.1:8787/api/program/1/ -H 'Authorization: Bearer <TOKEN>'
/// ```
///
/// * get a program range between two dates
/// ```BASH
/// curl -X GET http://127.0.0.1:8787/api/program/1/?start_after=2022-11-13T12:00:00&start_before=2022-11-20T11:59:59 \
/// -H 'Authorization: Bearer <TOKEN>'
/// ```
///
/// * get program from give day
/// ```BASH
/// curl -X GET http://127.0.0.1:8787/api/program/1/?start_after=2022-11-13T10:00:00 \
/// -H 'Authorization: Bearer <TOKEN>'
/// ```
#[get("/program/{id}/")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_program(
pool: web::Data<Pool<Sqlite>>,
id: web::Path<i32>,
obj: web::Query<ProgramObj>,
) -> Result<impl Responder, ServiceError> {
let (config, _) = playout_config(&pool.clone().into_inner(), &id).await?;
let start_sec = config.playlist.start_sec.unwrap();
let mut days = 0;
let mut program = vec![];
let after = obj.start_after;
let mut before = obj.start_before;
if after > before {
before = chrono::Local
.with_ymd_and_hms(after.year(), after.month(), after.day(), 23, 59, 59)
.unwrap()
.naive_local()
}
if start_sec > time_to_sec(&after.format("%H:%M:%S").to_string()) {
days = 1;
}
let date_range = get_date_range(&vec_strings![
(after - Duration::days(days)).format("%Y-%m-%d"),
"-",
before.format("%Y-%m-%d")
]);
for date in date_range {
let conn = pool.clone().into_inner();
let mut naive = NaiveDateTime::parse_from_str(
&format!("{date} {}", sec_to_time(start_sec)),
"%Y-%m-%d %H:%M:%S%.3f",
)
.unwrap();
let playlist = match read_playlist(&conn, *id, date.clone()).await {
Ok(p) => p,
Err(e) => {
error!("Error in Playlist from {date}: {e}");
continue;
}
};
for item in playlist.program {
let start: DateTime<Local> = Local.from_local_datetime(&naive).unwrap();
let source = match Regex::new(&config.text.regex)
.ok()
.and_then(|r| r.captures(&item.source))
{
Some(t) => t[1].to_string(),
None => item.source,
};
let p_item = ProgramItem {
source,
start: start.format("%Y-%m-%d %H:%M:%S%.3f%:z").to_string(),
r#in: item.seek,
out: item.out,
duration: item.duration,
category: item.category,
};
if naive >= after && naive <= before {
program.push(p_item);
}
naive += Duration::milliseconds(((item.out - item.seek) * 1000.0) as i64);
}
}
Ok(web::Json(program))
}

View File

@ -5,7 +5,7 @@ use argon2::{
use rand::{distributions::Alphanumeric, Rng};
use simplelog::*;
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool};
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite};
use crate::db::models::{Channel, TextPreset, User};
use crate::utils::{db_path, local_utc_offset, GlobalSettings};
@ -15,8 +15,7 @@ struct Role {
name: String,
}
async fn create_schema() -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
async fn create_schema(conn: &Pool<Sqlite>) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS global
(
@ -71,18 +70,19 @@ async fn create_schema() -> Result<SqliteQueryResult, sqlx::Error> {
FOREIGN KEY (channel_id) REFERENCES channels (id) ON UPDATE SET NULL ON DELETE SET NULL,
UNIQUE(mail, username)
);";
let result = sqlx::query(query).execute(&conn).await;
conn.close().await;
result
sqlx::query(query).execute(conn).await
}
pub async fn db_init(domain: Option<String>) -> Result<&'static str, Box<dyn std::error::Error>> {
pub async fn db_init(
conn: &Pool<Sqlite>,
domain: Option<String>,
) -> Result<&'static str, Box<dyn std::error::Error>> {
let db_path = db_path()?;
if !Sqlite::database_exists(&db_path).await.unwrap_or(false) {
Sqlite::create_database(&db_path).await.unwrap();
match create_schema().await {
match create_schema(conn).await {
Ok(_) => info!("Database created Successfully"),
Err(e) => panic!("{e}"),
}
@ -93,8 +93,6 @@ pub async fn db_init(domain: Option<String>) -> Result<&'static str, Box<dyn std
.map(char::from)
.collect();
let instances = connection().await?;
let url = match domain {
Some(d) => format!("http://{d}/live/stream.m3u8"),
None => "http://localhost/live/stream.m3u8".to_string(),
@ -120,45 +118,30 @@ pub async fn db_init(domain: Option<String>) -> Result<&'static str, Box<dyn std
sqlx::query(query)
.bind(secret)
.bind(url)
.execute(&instances)
.execute(conn)
.await?;
instances.close().await;
Ok("Database initialized!")
}
pub async fn connection() -> Result<Pool<Sqlite>, sqlx::Error> {
let db_path = db_path().unwrap();
let conn = SqlitePool::connect(&db_path).await?;
Ok(conn)
}
pub async fn select_global() -> Result<GlobalSettings, sqlx::Error> {
let conn = connection().await?;
pub async fn select_global(conn: &Pool<Sqlite>) -> Result<GlobalSettings, sqlx::Error> {
let query = "SELECT secret FROM global WHERE id = 1";
let result: GlobalSettings = sqlx::query_as(query).fetch_one(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query_as(query).fetch_one(conn).await
}
pub async fn select_channel(id: &i32) -> Result<Channel, sqlx::Error> {
let conn = connection().await?;
pub async fn select_channel(conn: &Pool<Sqlite>, id: &i32) -> Result<Channel, sqlx::Error> {
let query = "SELECT * FROM channels WHERE id = $1";
let mut result: Channel = sqlx::query_as(query).bind(id).fetch_one(&conn).await?;
conn.close().await;
let mut result: Channel = sqlx::query_as(query).bind(id).fetch_one(conn).await?;
result.utc_offset = local_utc_offset();
Ok(result)
}
pub async fn select_all_channels() -> Result<Vec<Channel>, sqlx::Error> {
let conn = connection().await?;
pub async fn select_all_channels(conn: &Pool<Sqlite>) -> Result<Vec<Channel>, sqlx::Error> {
let query = "SELECT * FROM channels";
let mut results: Vec<Channel> = sqlx::query_as(query).fetch_all(&conn).await?;
conn.close().await;
let mut results: Vec<Channel> = sqlx::query_as(query).fetch_all(conn).await?;
for result in results.iter_mut() {
result.utc_offset = local_utc_offset();
@ -167,26 +150,24 @@ pub async fn select_all_channels() -> Result<Vec<Channel>, sqlx::Error> {
Ok(results)
}
pub async fn update_channel(id: i32, channel: Channel) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn update_channel(
conn: &Pool<Sqlite>,
id: i32,
channel: Channel,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "UPDATE channels SET name = $2, preview_url = $3, config_path = $4, extra_extensions = $5 WHERE id = $1";
let result: SqliteQueryResult = sqlx::query(query)
sqlx::query(query)
.bind(id)
.bind(channel.name)
.bind(channel.preview_url)
.bind(channel.config_path)
.bind(channel.extra_extensions)
.execute(&conn)
.await?;
conn.close().await;
Ok(result)
.execute(conn)
.await
}
pub async fn insert_channel(channel: Channel) -> Result<Channel, sqlx::Error> {
let conn = connection().await?;
pub async fn insert_channel(conn: &Pool<Sqlite>, channel: Channel) -> Result<Channel, sqlx::Error> {
let query = "INSERT INTO channels (name, preview_url, config_path, extra_extensions, service) VALUES($1, $2, $3, $4, $5)";
let result = sqlx::query(query)
.bind(channel.name)
@ -194,56 +175,47 @@ pub async fn insert_channel(channel: Channel) -> Result<Channel, sqlx::Error> {
.bind(channel.config_path)
.bind(channel.extra_extensions)
.bind(channel.service)
.execute(&conn)
.execute(conn)
.await?;
let new_channel: Channel = sqlx::query_as("SELECT * FROM channels WHERE id = $1")
sqlx::query_as("SELECT * FROM channels WHERE id = $1")
.bind(result.last_insert_rowid())
.fetch_one(&conn)
.await?;
conn.close().await;
Ok(new_channel)
.fetch_one(conn)
.await
}
pub async fn delete_channel(id: &i32) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn delete_channel(
conn: &Pool<Sqlite>,
id: &i32,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "DELETE FROM channels WHERE id = $1";
let result: SqliteQueryResult = sqlx::query(query).bind(id).execute(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query(query).bind(id).execute(conn).await
}
pub async fn select_role(id: &i32) -> Result<String, sqlx::Error> {
let conn = connection().await?;
pub async fn select_role(conn: &Pool<Sqlite>, id: &i32) -> Result<String, sqlx::Error> {
let query = "SELECT name FROM roles WHERE id = $1";
let result: Role = sqlx::query_as(query).bind(id).fetch_one(&conn).await?;
conn.close().await;
let result: Role = sqlx::query_as(query).bind(id).fetch_one(conn).await?;
Ok(result.name)
}
pub async fn select_login(user: &str) -> Result<User, sqlx::Error> {
let conn = connection().await?;
pub async fn select_login(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::Error> {
let query = "SELECT id, mail, username, password, salt, role_id FROM user WHERE username = $1";
let result: User = sqlx::query_as(query).bind(user).fetch_one(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query_as(query).bind(user).fetch_one(conn).await
}
pub async fn select_user(user: &str) -> Result<User, sqlx::Error> {
let conn = connection().await?;
pub async fn select_user(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::Error> {
let query = "SELECT id, mail, username, role_id FROM user WHERE username = $1";
let result: User = sqlx::query_as(query).bind(user).fetch_one(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query_as(query).bind(user).fetch_one(conn).await
}
pub async fn insert_user(user: User) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn insert_user(
conn: &Pool<Sqlite>,
user: User,
) -> Result<SqliteQueryResult, sqlx::Error> {
let salt = SaltString::generate(&mut OsRng);
let password_hash = Argon2::default()
.hash_password(user.password.clone().as_bytes(), &salt)
@ -251,43 +223,43 @@ pub async fn insert_user(user: User) -> Result<SqliteQueryResult, sqlx::Error> {
let query =
"INSERT INTO user (mail, username, password, salt, role_id) VALUES($1, $2, $3, $4, $5)";
let result = sqlx::query(query)
sqlx::query(query)
.bind(user.mail)
.bind(user.username)
.bind(password_hash.to_string())
.bind(salt.to_string())
.bind(user.role_id)
.execute(&conn)
.await?;
conn.close().await;
Ok(result)
.execute(conn)
.await
}
pub async fn update_user(id: i32, fields: String) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn update_user(
conn: &Pool<Sqlite>,
id: i32,
fields: String,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = format!("UPDATE user SET {fields} WHERE id = $1");
let result: SqliteQueryResult = sqlx::query(&query).bind(id).execute(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query(&query).bind(id).execute(conn).await
}
pub async fn select_presets(id: i32) -> Result<Vec<TextPreset>, sqlx::Error> {
let conn = connection().await?;
pub async fn select_presets(conn: &Pool<Sqlite>, id: i32) -> Result<Vec<TextPreset>, sqlx::Error> {
let query = "SELECT * FROM presets WHERE channel_id = $1";
let result: Vec<TextPreset> = sqlx::query_as(query).bind(id).fetch_all(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query_as(query).bind(id).fetch_all(conn).await
}
pub async fn update_preset(id: &i32, preset: TextPreset) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn update_preset(
conn: &Pool<Sqlite>,
id: &i32,
preset: TextPreset,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query =
"UPDATE presets SET name = $1, text = $2, x = $3, y = $4, fontsize = $5, line_spacing = $6,
fontcolor = $7, alpha = $8, box = $9, boxcolor = $10, boxborderw = 11 WHERE id = $12";
let result: SqliteQueryResult = sqlx::query(query)
sqlx::query(query)
.bind(preset.name)
.bind(preset.text)
.bind(preset.x)
@ -300,19 +272,19 @@ pub async fn update_preset(id: &i32, preset: TextPreset) -> Result<SqliteQueryRe
.bind(preset.boxcolor)
.bind(preset.boxborderw)
.bind(id)
.execute(&conn)
.await?;
conn.close().await;
Ok(result)
.execute(conn)
.await
}
pub async fn insert_preset(preset: TextPreset) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn insert_preset(
conn: &Pool<Sqlite>,
preset: TextPreset,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query =
"INSERT INTO presets (channel_id, name, text, x, y, fontsize, line_spacing, fontcolor, alpha, box, boxcolor, boxborderw)
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)";
let result: SqliteQueryResult = sqlx::query(query)
sqlx::query(query)
.bind(preset.channel_id)
.bind(preset.name)
.bind(preset.text)
@ -325,18 +297,15 @@ pub async fn insert_preset(preset: TextPreset) -> Result<SqliteQueryResult, sqlx
.bind(preset.r#box)
.bind(preset.boxcolor)
.bind(preset.boxborderw)
.execute(&conn)
.await?;
conn.close().await;
Ok(result)
.execute(conn)
.await
}
pub async fn delete_preset(id: &i32) -> Result<SqliteQueryResult, sqlx::Error> {
let conn = connection().await?;
pub async fn delete_preset(
conn: &Pool<Sqlite>,
id: &i32,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "DELETE FROM presets WHERE id = $1;";
let result: SqliteQueryResult = sqlx::query(query).bind(id).execute(&conn).await?;
conn.close().await;
Ok(result)
sqlx::query(query).bind(id).execute(conn).await
}

View File

@ -1,2 +1,13 @@
use sqlx::{Pool, Sqlite, SqlitePool};
pub mod handles;
pub mod models;
use crate::utils::db_path;
pub async fn db_pool() -> Result<Pool<Sqlite>, sqlx::Error> {
let db_path = db_path().unwrap();
let conn = SqlitePool::connect(&db_path).await?;
Ok(conn)
}

View File

@ -18,13 +18,13 @@ use api::{
routes::{
add_channel, add_dir, add_preset, add_user, control_playout, del_playlist, delete_preset,
file_browser, gen_playlist, get_all_channels, get_channel, get_log, get_playlist,
get_playout_config, get_presets, get_user, import_playlist, login, media_current,
media_last, media_next, move_rename, patch_channel, process_control, remove,
get_playout_config, get_presets, get_program, get_user, import_playlist, login,
media_current, media_last, media_next, move_rename, patch_channel, process_control, remove,
remove_channel, save_file, save_playlist, send_text_message, update_playout_config,
update_preset, update_user,
},
};
use db::models::LoginUser;
use db::{db_pool, models::LoginUser};
use utils::{args_parse::Args, db_path, init_config, run_args, Role};
use ffplayout_lib::utils::{init_logging, PlayoutConfig};
@ -64,7 +64,15 @@ async fn main() -> std::io::Result<()> {
let logging = init_logging(&config, None, None);
CombinedLogger::init(logging).unwrap();
if let Err(c) = run_args(args.clone()).await {
let pool = match db_pool().await {
Ok(p) => p,
Err(e) => {
error!("{e}");
exit(1);
}
};
if let Err(c) = run_args(&pool, args.clone()).await {
exit(c);
}
@ -75,7 +83,7 @@ async fn main() -> std::io::Result<()> {
exit(1);
}
}
init_config().await;
init_config(&pool).await;
let ip_port = conn.split(':').collect::<Vec<&str>>();
let addr = ip_port[0];
let port = ip_port[1].parse::<u16>().unwrap();
@ -85,7 +93,10 @@ async fn main() -> std::io::Result<()> {
// no allow origin here, give it to the reverse proxy
HttpServer::new(move || {
let auth = HttpAuthentication::bearer(validator);
let db_pool = web::Data::new(pool.clone());
App::new()
.app_data(db_pool)
.wrap(middleware::Logger::default())
.service(login)
.service(
@ -121,7 +132,8 @@ async fn main() -> std::io::Result<()> {
.service(move_rename)
.service(remove)
.service(save_file)
.service(import_playlist),
.service(import_playlist)
.service(get_program),
)
.service(Files::new("/", public_path()).index_file("index.html"))
})

View File

@ -1,12 +1,16 @@
use std::fs;
use simplelog::*;
use sqlx::{Pool, Sqlite};
use crate::utils::{control::control_service, errors::ServiceError};
use crate::db::{handles, models::Channel};
pub async fn create_channel(target_channel: Channel) -> Result<Channel, ServiceError> {
pub async fn create_channel(
conn: &Pool<Sqlite>,
target_channel: Channel,
) -> Result<Channel, ServiceError> {
if !target_channel.service.starts_with("ffplayout@") {
return Err(ServiceError::BadRequest("Bad service name!".to_string()));
}
@ -20,22 +24,22 @@ pub async fn create_channel(target_channel: Channel) -> Result<Channel, ServiceE
&target_channel.config_path,
)?;
let new_channel = handles::insert_channel(target_channel).await?;
control_service(new_channel.id, "enable").await?;
let new_channel = handles::insert_channel(conn, target_channel).await?;
control_service(conn, new_channel.id, "enable").await?;
Ok(new_channel)
}
pub async fn delete_channel(id: i32) -> Result<(), ServiceError> {
let channel = handles::select_channel(&id).await?;
control_service(channel.id, "stop").await?;
control_service(channel.id, "disable").await?;
pub async fn delete_channel(conn: &Pool<Sqlite>, id: i32) -> Result<(), ServiceError> {
let channel = handles::select_channel(conn, &id).await?;
control_service(conn, channel.id, "stop").await?;
control_service(conn, channel.id, "disable").await?;
if let Err(e) = fs::remove_file(channel.config_path) {
error!("{e}");
};
handles::delete_channel(&id).await?;
handles::delete_channel(conn, &id).await?;
Ok(())
}

View File

@ -5,6 +5,7 @@ use reqwest::{
Client, Response,
};
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Sqlite};
use crate::db::handles::select_channel;
use crate::utils::{errors::ServiceError, playout_config};
@ -56,8 +57,8 @@ struct SystemD {
}
impl SystemD {
async fn new(id: i32) -> Result<Self, ServiceError> {
let channel = select_channel(&id).await?;
async fn new(conn: &Pool<Sqlite>, id: i32) -> Result<Self, ServiceError> {
let channel = select_channel(conn, &id).await?;
Ok(Self {
service: channel.service,
@ -130,11 +131,15 @@ fn create_header(auth: &str) -> HeaderMap {
headers
}
async fn post_request<T>(id: i32, obj: RpcObj<T>) -> Result<Response, ServiceError>
async fn post_request<T>(
conn: &Pool<Sqlite>,
id: i32,
obj: RpcObj<T>,
) -> Result<Response, ServiceError>
where
T: Serialize,
{
let (config, _) = playout_config(&id).await?;
let (config, _) = playout_config(conn, &id).await?;
let url = format!("http://{}", config.rpc_server.address);
let client = Client::new();
@ -151,6 +156,7 @@ where
}
pub async fn send_message(
conn: &Pool<Sqlite>,
id: i32,
message: HashMap<String, String>,
) -> Result<Response, ServiceError> {
@ -163,23 +169,35 @@ pub async fn send_message(
},
);
post_request(id, json_obj).await
post_request(conn, id, json_obj).await
}
pub async fn control_state(id: i32, command: String) -> Result<Response, ServiceError> {
pub async fn control_state(
conn: &Pool<Sqlite>,
id: i32,
command: String,
) -> Result<Response, ServiceError> {
let json_obj = RpcObj::new(id, "player".into(), ControlParams { control: command });
post_request(id, json_obj).await
post_request(conn, id, json_obj).await
}
pub async fn media_info(id: i32, command: String) -> Result<Response, ServiceError> {
pub async fn media_info(
conn: &Pool<Sqlite>,
id: i32,
command: String,
) -> Result<Response, ServiceError> {
let json_obj = RpcObj::new(id, "player".into(), MediaParams { media: command });
post_request(id, json_obj).await
post_request(conn, id, json_obj).await
}
pub async fn control_service(id: i32, command: &str) -> Result<String, ServiceError> {
let system_d = SystemD::new(id).await?;
pub async fn control_service(
conn: &Pool<Sqlite>,
id: i32,
command: &str,
) -> Result<String, ServiceError> {
let system_d = SystemD::new(conn, id).await?;
match command {
"enable" => system_d.enable(),

View File

@ -6,6 +6,7 @@ use futures_util::TryStreamExt as _;
use rand::{distributions::Alphanumeric, Rng};
use relative_path::RelativePath;
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Sqlite};
use simplelog::*;
@ -87,8 +88,12 @@ fn norm_abs_path(root_path: &str, input_path: &str) -> (PathBuf, String, String)
/// Take input path and give file and folder list from it back.
/// Input should be a relative path segment, but when it is a absolut path, the norm_abs_path function
/// will take care, that user can not break out from given storage path in config.
pub async fn browser(id: i32, path_obj: &PathObject) -> Result<PathObject, ServiceError> {
let (config, _) = playout_config(&id).await?;
pub async fn browser(
conn: &Pool<Sqlite>,
id: i32,
path_obj: &PathObject,
) -> Result<PathObject, ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let extensions = config.storage.extensions;
let (path, parent, path_component) = norm_abs_path(&config.storage.path, &path_obj.source);
let mut obj = PathObject::new(path_component, Some(parent));
@ -143,10 +148,11 @@ pub async fn browser(id: i32, path_obj: &PathObject) -> Result<PathObject, Servi
}
pub async fn create_directory(
conn: &Pool<Sqlite>,
id: i32,
path_obj: &PathObject,
) -> Result<HttpResponse, ServiceError> {
let (config, _) = playout_config(&id).await?;
let (config, _) = playout_config(conn, &id).await?;
let (path, _, _) = norm_abs_path(&config.storage.path, &path_obj.source);
if let Err(e) = fs::create_dir_all(&path) {
@ -198,8 +204,12 @@ fn rename(source: &PathBuf, target: &PathBuf) -> Result<MoveObject, ServiceError
}
}
pub async fn rename_file(id: i32, move_object: &MoveObject) -> Result<MoveObject, ServiceError> {
let (config, _) = playout_config(&id).await?;
pub async fn rename_file(
conn: &Pool<Sqlite>,
id: i32,
move_object: &MoveObject,
) -> Result<MoveObject, ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let (source_path, _, _) = norm_abs_path(&config.storage.path, &move_object.source);
let (mut target_path, _, _) = norm_abs_path(&config.storage.path, &move_object.target);
@ -229,8 +239,12 @@ pub async fn rename_file(id: i32, move_object: &MoveObject) -> Result<MoveObject
Err(ServiceError::InternalServerError)
}
pub async fn remove_file_or_folder(id: i32, source_path: &str) -> Result<(), ServiceError> {
let (config, _) = playout_config(&id).await?;
pub async fn remove_file_or_folder(
conn: &Pool<Sqlite>,
id: i32,
source_path: &str,
) -> Result<(), ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let (source, _, _) = norm_abs_path(&config.storage.path, source_path);
if !source.exists() {
@ -262,8 +276,8 @@ pub async fn remove_file_or_folder(id: i32, source_path: &str) -> Result<(), Ser
Err(ServiceError::InternalServerError)
}
async fn valid_path(id: i32, path: &str) -> Result<PathBuf, ServiceError> {
let (config, _) = playout_config(&id).await?;
async fn valid_path(conn: &Pool<Sqlite>, id: i32, path: &str) -> Result<PathBuf, ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let (test_path, _, _) = norm_abs_path(&config.storage.path, path);
if !test_path.is_dir() {
@ -274,6 +288,7 @@ async fn valid_path(id: i32, path: &str) -> Result<PathBuf, ServiceError> {
}
pub async fn upload(
conn: &Pool<Sqlite>,
id: i32,
mut payload: Multipart,
path: &str,
@ -296,7 +311,7 @@ pub async fn upload(
if abs_path {
filepath = PathBuf::from(path);
} else {
let target_path = valid_path(id, path).await?;
let target_path = valid_path(conn, id, path).await?;
filepath = target_path.join(filename);
}

View File

@ -5,11 +5,13 @@ use std::{
path::Path,
};
use chrono::prelude::*;
use chrono::{format::ParseErrorKind, prelude::*};
use faccess::PathExt;
use once_cell::sync::OnceCell;
use rpassword::read_password;
use serde::{de, Deserialize, Deserializer};
use simplelog::*;
use sqlx::{Pool, Sqlite};
pub mod args_parse;
pub mod channels;
@ -23,7 +25,7 @@ use crate::db::{
models::{Channel, User},
};
use crate::utils::{args_parse::Args, errors::ServiceError};
use ffplayout_lib::utils::PlayoutConfig;
use ffplayout_lib::utils::{time_to_sec, PlayoutConfig};
#[derive(Clone, Eq, PartialEq)]
pub enum Role {
@ -48,8 +50,8 @@ pub struct GlobalSettings {
}
impl GlobalSettings {
async fn new() -> Self {
let global_settings = select_global();
async fn new(conn: &Pool<Sqlite>) -> Self {
let global_settings = select_global(conn);
match global_settings.await {
Ok(g) => g,
@ -66,8 +68,8 @@ impl GlobalSettings {
static INSTANCE: OnceCell<GlobalSettings> = OnceCell::new();
pub async fn init_config() {
let config = GlobalSettings::new().await;
pub async fn init_config(conn: &Pool<Sqlite>) {
let config = GlobalSettings::new(conn).await;
INSTANCE.set(config).unwrap();
}
@ -88,7 +90,7 @@ pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
Ok(db_path)
}
pub async fn run_args(mut args: Args) -> Result<(), i32> {
pub async fn run_args(conn: &Pool<Sqlite>, mut args: Args) -> Result<(), i32> {
if !args.init && args.listen.is_none() && !args.ask && args.username.is_none() {
error!("Wrong number of arguments! Run ffpapi --help for more information.");
@ -96,7 +98,7 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> {
}
if args.init {
if let Err(e) = db_init(args.domain).await {
if let Err(e) = db_init(conn, args.domain).await {
panic!("{e}");
};
@ -160,7 +162,7 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> {
token: None,
};
if let Err(e) = insert_user(user).await {
if let Err(e) = insert_user(conn, user).await {
error!("{e}");
return Err(1);
};
@ -175,13 +177,18 @@ pub async fn run_args(mut args: Args) -> Result<(), i32> {
pub fn read_playout_config(path: &str) -> Result<PlayoutConfig, Box<dyn Error>> {
let file = File::open(path)?;
let config: PlayoutConfig = serde_yaml::from_reader(file)?;
let mut config: PlayoutConfig = serde_yaml::from_reader(file)?;
config.playlist.start_sec = Some(time_to_sec(&config.playlist.day_start));
Ok(config)
}
pub async fn playout_config(channel_id: &i32) -> Result<(PlayoutConfig, Channel), ServiceError> {
if let Ok(channel) = select_channel(channel_id).await {
pub async fn playout_config(
conn: &Pool<Sqlite>,
channel_id: &i32,
) -> Result<(PlayoutConfig, Channel), ServiceError> {
if let Ok(channel) = select_channel(conn, channel_id).await {
if let Ok(config) = read_playout_config(&channel.config_path.clone()) {
return Ok((config, channel));
}
@ -192,8 +199,12 @@ pub async fn playout_config(channel_id: &i32) -> Result<(PlayoutConfig, Channel)
))
}
pub async fn read_log_file(channel_id: &i32, date: &str) -> Result<String, ServiceError> {
if let Ok(channel) = select_channel(channel_id).await {
pub async fn read_log_file(
conn: &Pool<Sqlite>,
channel_id: &i32,
date: &str,
) -> Result<String, ServiceError> {
if let Ok(channel) = select_channel(conn, channel_id).await {
let mut date_str = "".to_string();
if !date.is_empty() {
@ -234,3 +245,22 @@ pub fn local_utc_offset() -> i32 {
utc_offset
}
pub fn naive_date_time_from_str<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
match NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S") {
Ok(date_time) => Ok(date_time),
Err(e) => {
if e.kind() == ParseErrorKind::TooShort {
NaiveDateTime::parse_from_str(&format!("{s}T00:00:00"), "%Y-%m-%dT%H:%M:%S")
.map_err(de::Error::custom)
} else {
NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%#z").map_err(de::Error::custom)
}
}
}
}

View File

@ -1,14 +1,19 @@
use std::{fs, path::PathBuf};
use simplelog::*;
use sqlx::{Pool, Sqlite};
use crate::utils::{errors::ServiceError, playout_config};
use ffplayout_lib::utils::{
generate_playlist as playlist_generator, json_reader, json_writer, JsonPlaylist,
};
pub async fn read_playlist(id: i32, date: String) -> Result<JsonPlaylist, ServiceError> {
let (config, _) = playout_config(&id).await?;
pub async fn read_playlist(
conn: &Pool<Sqlite>,
id: i32,
date: String,
) -> Result<JsonPlaylist, ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let mut playlist_path = PathBuf::from(&config.playlist.path);
let d: Vec<&str> = date.split('-').collect();
playlist_path = playlist_path
@ -23,8 +28,12 @@ pub async fn read_playlist(id: i32, date: String) -> Result<JsonPlaylist, Servic
}
}
pub async fn write_playlist(id: i32, json_data: JsonPlaylist) -> Result<String, ServiceError> {
let (config, _) = playout_config(&id).await?;
pub async fn write_playlist(
conn: &Pool<Sqlite>,
id: i32,
json_data: JsonPlaylist,
) -> Result<String, ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let date = json_data.date.clone();
let mut playlist_path = PathBuf::from(&config.playlist.path);
let d: Vec<&str> = date.split('-').collect();
@ -68,8 +77,12 @@ pub async fn write_playlist(id: i32, json_data: JsonPlaylist) -> Result<String,
Err(ServiceError::InternalServerError)
}
pub async fn generate_playlist(id: i32, date: String) -> Result<JsonPlaylist, ServiceError> {
let (mut config, channel) = playout_config(&id).await?;
pub async fn generate_playlist(
conn: &Pool<Sqlite>,
id: i32,
date: String,
) -> Result<JsonPlaylist, ServiceError> {
let (mut config, channel) = playout_config(conn, &id).await?;
config.general.generate = Some(vec![date.clone()]);
match playlist_generator(&config, Some(channel.name)) {
@ -89,8 +102,8 @@ pub async fn generate_playlist(id: i32, date: String) -> Result<JsonPlaylist, Se
}
}
pub async fn delete_playlist(id: i32, date: &str) -> Result<(), ServiceError> {
let (config, _) = playout_config(&id).await?;
pub async fn delete_playlist(conn: &Pool<Sqlite>, id: i32, date: &str) -> Result<(), ServiceError> {
let (config, _) = playout_config(conn, &id).await?;
let mut playlist_path = PathBuf::from(&config.playlist.path);
let d: Vec<&str> = date.split('-').collect();
playlist_path = playlist_path

View File

@ -4,7 +4,7 @@ description = "24/7 playout based on rust and ffmpeg"
license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md"
version = "0.16.3"
version = "0.16.4"
edition = "2021"
default-run = "ffplayout"

@ -1 +1 @@
Subproject commit 535624d81bc58721314e7c392eafe8614b0384f8
Subproject commit 269d3f0306a230805f7a19477163631c07bbd0a3

View File

@ -4,7 +4,7 @@ description = "Library for ffplayout"
license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md"
version = "0.16.3"
version = "0.16.4"
edition = "2021"
[dependencies]

View File

@ -14,41 +14,12 @@ use std::{
sync::{atomic::AtomicUsize, Arc, Mutex},
};
use chrono::{Duration, NaiveDate};
use simplelog::*;
use super::folder::FolderSource;
use crate::utils::{json_serializer::JsonPlaylist, time_to_sec, Media, PlayoutConfig};
/// Generate a vector with dates, from given range.
fn get_date_range(date_range: &[String]) -> Vec<String> {
let mut range = vec![];
let start = match NaiveDate::parse_from_str(&date_range[0], "%Y-%m-%d") {
Ok(s) => s,
Err(_) => {
error!("date format error in: <yellow>{:?}</>", date_range[0]);
exit(1);
}
};
let end = match NaiveDate::parse_from_str(&date_range[2], "%Y-%m-%d") {
Ok(e) => e,
Err(_) => {
error!("date format error in: <yellow>{:?}</>", date_range[2]);
exit(1);
}
};
let duration = end.signed_duration_since(start);
let days = duration.num_days() + 1;
for day in 0..days {
range.push((start + Duration::days(day)).format("%Y-%m-%d").to_string());
}
range
}
use crate::utils::{
get_date_range, json_serializer::JsonPlaylist, time_to_sec, Media, PlayoutConfig,
};
/// Generate playlists
pub fn generate_playlist(

View File

@ -797,6 +797,36 @@ pub fn test_tcp_port(url: &str) -> bool {
false
}
/// Generate a vector with dates, from given range.
pub fn get_date_range(date_range: &[String]) -> Vec<String> {
let mut range = vec![];
let start = match NaiveDate::parse_from_str(&date_range[0], "%Y-%m-%d") {
Ok(s) => s,
Err(_) => {
error!("date format error in: <yellow>{:?}</>", date_range[0]);
exit(1);
}
};
let end = match NaiveDate::parse_from_str(&date_range[2], "%Y-%m-%d") {
Ok(e) => e,
Err(_) => {
error!("date format error in: <yellow>{:?}</>", date_range[2]);
exit(1);
}
};
let duration = end.signed_duration_since(start);
let days = duration.num_days() + 1;
for day in 0..days {
range.push((start + Duration::days(day)).format("%Y-%m-%d").to_string());
}
range
}
pub fn home_dir() -> Option<PathBuf> {
home_dir_inner()
}