Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added REST service endpoints #2

Merged
merged 17 commits into from
Aug 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ Cargo.lock

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

.DS_Store
temp/
3 changes: 2 additions & 1 deletion movies-db-service/movies-db-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ movies-db = { path = "../movies-db" }
log = "0.4"
anyhow = "1.0"
simple-logging = "2.0"
clap = { version = "4.2", features = ["derive"] }
clap = { version = "4.2", features = ["derive"] }
actix-web = "4"
20 changes: 13 additions & 7 deletions movies-db-service/movies-db-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ mod options;

use anyhow::Result;
use log::{error, info};
use movies_db::{file_storage::FileStorage, Options as ServiceOptions, Service, SimpleMoviesIndex};
use options::Options;

use clap::{Parser};
use clap::Parser;

use log::{LevelFilter};
use log::LevelFilter;

/// Parses the program arguments and returns None, if no arguments were provided and Some otherwise.
fn parse_args() -> Result<Options> {
Expand All @@ -19,17 +20,22 @@ fn initialize_logging(filter: LevelFilter) {
simple_logging::log_to(std::io::stdout(), filter);
}


/// Runs the program.
fn run_program() -> Result<()> {
async fn run_program() -> Result<()> {
let options = parse_args()?;
initialize_logging(LevelFilter::from(options.log_level));

let service_options: ServiceOptions = options.into();

let service: Service<SimpleMoviesIndex, FileStorage> = Service::new(&service_options)?;
service.run().await?;

Ok(())
}

fn main() {
match run_program() {
#[actix_web::main]
async fn main() {
match run_program().await {
Ok(()) => {
info!("SUCCESS");
}
Expand All @@ -40,4 +46,4 @@ fn main() {
std::process::exit(-1);
}
}
}
}
18 changes: 16 additions & 2 deletions movies-db-service/movies-db-cli/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use log::LevelFilter;

use movies_db::Options as ServiceOptions;

#[derive(ValueEnum, Clone, Copy, Debug)]
pub enum LogLevel {
Trace,
Expand All @@ -24,7 +26,6 @@ impl From<LogLevel> for LevelFilter {
}
}


/// CLI interface to test different occlusion culler algorithms.
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
Expand All @@ -33,7 +34,20 @@ pub struct Options {
#[arg(short, value_enum, long, default_value_t = LogLevel::Info)]
pub log_level: LogLevel,

/// The address to bind the http server to
#[arg(short, value_enum, long, default_value = "0.0.0.0:3030")]
pub address: String,

/// The path to the root directory
#[arg(short, long)]
pub root_dir: PathBuf,
}
}

impl Into<ServiceOptions> for Options {
fn into(self) -> ServiceOptions {
ServiceOptions {
root_dir: self.root_dir,
http_address: self.address.parse().unwrap(),
}
}
}
8 changes: 8 additions & 0 deletions movies-db-service/movies-db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ itertools = "0.11"
uuid = { version = "1.4", features = ["v4", "fast-rng", "macro-diagnostics"] }
chrono = { version = "0.4", features = ["serde"] }
wildmatch = "2.1"
actix-web = "4"
actix-multipart = "0.6"
tokio = { version = "1", features = ["full"] }
tokio-util = "0.7"
futures = { version = "0.3" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
async-trait = "0.1"
actix-cors = "0.6"

[dev-dependencies]
tempdir = "0.3"
Expand Down
88 changes: 55 additions & 33 deletions movies-db-service/movies-db/src/db/movies_index.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::ops::Range;

use crate::{Error, MovieId, Options};

use chrono::{DateTime, Utc};
Expand All @@ -12,16 +10,19 @@ pub struct Movie {
pub title: String,

/// An optional description of the movie.
#[serde(default)]
pub description: String,

/// A list of tags associated with the movie.
#[serde(default)]
pub tags: Vec<String>,
}

/// A single movie entry with timestamp.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MovieWithDate {
pub struct MovieDetailed {
pub movie: Movie,
pub movie_file_info: Option<MovieFileInfo>,
pub date: DateTime<Utc>,
}

Expand All @@ -35,6 +36,12 @@ pub enum SortingField {
Date,
}

impl Default for SortingField {
fn default() -> Self {
Self::Date
}
}

/// The sorting order for the movies.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub enum SortingOrder {
Expand All @@ -45,27 +52,32 @@ pub enum SortingOrder {
Descending,
}

/// A sorting for the movies.
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct MovieSorting {
pub field: SortingField,
pub order: SortingOrder,
}

impl Default for MovieSorting {
impl Default for SortingOrder {
fn default() -> Self {
Self {
field: SortingField::Date,
order: SortingOrder::Descending,
}
Self::Descending
}
}

/// The file info for a stored movie file.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MovieFileInfo {
/// the extension of the movie file in lower case, e.g., "mp4"
pub extension: String,

// the mime type of the movie file, e.g., "video/mp4"
pub mime_type: String,
}

/// A query for searching movies in the database.
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct MovieSearchQuery {
/// The sorting being used for the movies.
pub sorting: MovieSorting,
/// The field used for sorting
#[serde(default)]
pub sorting_field: SortingField,

/// The order used for sorting
#[serde(default)]
pub sorting_order: SortingOrder,

/// Optionally, a search string for the title of the movie. If provided, only movies whose
/// title matches the search string will be returned.
Expand All @@ -76,13 +88,16 @@ pub struct MovieSearchQuery {
#[serde(default)]
pub tags: Vec<String>,

/// Optionally, a range for the items to return can be specified.
pub range: Option<Range<usize>>,
/// Optionally, the start index of the movies to return.
pub start_index: Option<usize>,

/// Optionally, the maximal number of results to return.
pub num_results: Option<usize>,
}

/// The movies index manages a list of all movies in the database.
/// Additionally, it provides methods for managing and searching movies.
pub trait MoviesIndex {
pub trait MoviesIndex: Send + Sync {
/// Creates a new instance of the movies index
///
/// # Arguments
Expand All @@ -101,7 +116,18 @@ pub trait MoviesIndex {
///
/// # Arguments
/// `id` - The ID of the movie to return.
fn get_movie(&self, id: &MovieId) -> Result<MovieWithDate, Error>;
fn get_movie(&self, id: &MovieId) -> Result<MovieDetailed, Error>;

/// Updates the movie file info for the given ID.
///
/// # Arguments
/// `id` - The ID of the movie to update.
/// `movie_file_info` - The new movie file info.
fn update_movie_file_info(
&mut self,
id: &MovieId,
movie_file_info: MovieFileInfo,
) -> Result<(), Error>;

/// Removes the movie for the given ID.
///
Expand Down Expand Up @@ -145,10 +171,8 @@ mod test {
fn test_query_serialization() {
let query_string = r#"
{
"sorting": {
"field": "title",
"order": "ascending"
},
"sorting_field": "title",
"sorting_order": "ascending",
"title": "foo",
"tags": ["bar", "baz"]
}
Expand All @@ -158,15 +182,13 @@ mod test {

assert_eq!(query.title, Some("foo".to_string()));
assert_eq!(query.tags, vec!["bar".to_string(), "baz".to_string()]);
assert_eq!(query.sorting.field, SortingField::Title);
assert_eq!(query.sorting.order, SortingOrder::Ascending);
assert_eq!(query.sorting_field, SortingField::Title);
assert_eq!(query.sorting_order, SortingOrder::Ascending);

let query_string = r#"
{
"sorting": {
"field": "date",
"order": "descending"
},
"sorting_field": "date",
"sorting_order": "descending",
"title": "foo"
}
"#;
Expand All @@ -175,7 +197,7 @@ mod test {

assert_eq!(query.title, Some("foo".to_string()));
assert!(query.tags.is_empty());
assert_eq!(query.sorting.field, SortingField::Date);
assert_eq!(query.sorting.order, SortingOrder::Descending);
assert_eq!(query.sorting_field, SortingField::Date);
assert_eq!(query.sorting_order, SortingOrder::Descending);
}
}
Loading