feature/tagging #16
@@ -100,6 +100,19 @@ pub struct PhotosResponse {
|
|||||||
pub dirs: Vec<String>,
|
pub dirs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct FilesRequest {
|
||||||
|
pub path: String,
|
||||||
|
pub tag_ids: Option<Vec<i32>>,
|
||||||
|
pub tag_filter_mode: Option<FilterMode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Deserialize)]
|
||||||
|
pub enum FilterMode {
|
||||||
|
Any,
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct ThumbnailRequest {
|
pub struct ThumbnailRequest {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
|||||||
35
src/files.rs
35
src/files.rs
@@ -2,6 +2,7 @@ use std::fmt::Debug;
|
|||||||
use std::fs::read_dir;
|
use std::fs::read_dir;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use ::anyhow;
|
use ::anyhow;
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
@@ -10,18 +11,19 @@ use actix_web::{
|
|||||||
web::{self, Query},
|
web::{self, Query},
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
|
|
||||||
use crate::data::{Claims, PhotosResponse, ThumbnailRequest};
|
use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse, ThumbnailRequest};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
|
use crate::tags::TagDao;
|
||||||
use path_absolutize::*;
|
use path_absolutize::*;
|
||||||
|
|
||||||
pub async fn list_photos(
|
pub async fn list_photos<TagD: TagDao>(
|
||||||
_: Claims,
|
_: Claims,
|
||||||
req: Query<ThumbnailRequest>,
|
req: Query<FilesRequest>,
|
||||||
app_state: web::Data<AppState>,
|
app_state: web::Data<AppState>,
|
||||||
|
tag_dao: web::Data<Mutex<TagD>>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let path = &req.path;
|
let path = &req.path;
|
||||||
if let Some(path) = is_valid_full_path(&PathBuf::from(&app_state.base_path), path) {
|
if let Some(path) = is_valid_full_path(&PathBuf::from(&app_state.base_path), path) {
|
||||||
@@ -44,6 +46,19 @@ pub async fn list_photos(
|
|||||||
relative.to_path_buf()
|
relative.to_path_buf()
|
||||||
})
|
})
|
||||||
.map(|f| f.to_str().unwrap().to_string())
|
.map(|f| f.to_str().unwrap().to_string())
|
||||||
|
.filter(|path| {
|
||||||
|
if let (Some(tag_ids), Ok(mut tag_dao)) = (&req.tag_ids, tag_dao.lock()) {
|
||||||
|
let filter_mode = &req.tag_filter_mode.unwrap_or(FilterMode::Any);
|
||||||
|
|
||||||
|
let file_tags = tag_dao.get_tags_for_path(path).unwrap_or_default();
|
||||||
|
return match filter_mode {
|
||||||
|
FilterMode::Any => file_tags.iter().any(|t| tag_ids.contains(&t.id)),
|
||||||
|
FilterMode::All => file_tags.iter().all(|t| tag_ids.contains(&t.id)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
let dirs = files
|
let dirs = files
|
||||||
@@ -153,6 +168,8 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use std::{fs, sync::Arc};
|
use std::{fs, sync::Arc};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use crate::tags::SqliteTagDao;
|
||||||
|
|
||||||
fn setup() {
|
fn setup() {
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
@@ -167,7 +184,7 @@ mod tests {
|
|||||||
exp: 12345,
|
exp: 12345,
|
||||||
};
|
};
|
||||||
|
|
||||||
let request: Query<ThumbnailRequest> = Query::from_query("path=").unwrap();
|
let request: Query<FilesRequest> = Query::from_query("path=").unwrap();
|
||||||
|
|
||||||
let mut temp_photo = std::env::temp_dir();
|
let mut temp_photo = std::env::temp_dir();
|
||||||
let mut tmp = temp_photo.clone();
|
let mut tmp = temp_photo.clone();
|
||||||
@@ -187,8 +204,9 @@ mod tests {
|
|||||||
String::from("/tmp"),
|
String::from("/tmp"),
|
||||||
String::from("/tmp/thumbs"),
|
String::from("/tmp/thumbs"),
|
||||||
)),
|
)),
|
||||||
|
Data::new(Mutex::new(SqliteTagDao::default())),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let status = response.status();
|
let status = response.status();
|
||||||
|
|
||||||
let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap();
|
let body: PhotosResponse = serde_json::from_str(&response.read_to_str()).unwrap();
|
||||||
@@ -215,7 +233,7 @@ mod tests {
|
|||||||
exp: 12345,
|
exp: 12345,
|
||||||
};
|
};
|
||||||
|
|
||||||
let request: Query<ThumbnailRequest> = Query::from_query("path=..").unwrap();
|
let request: Query<FilesRequest> = Query::from_query("path=..").unwrap();
|
||||||
|
|
||||||
let response = list_photos(
|
let response = list_photos(
|
||||||
claims,
|
claims,
|
||||||
@@ -225,8 +243,9 @@ mod tests {
|
|||||||
String::from("/tmp"),
|
String::from("/tmp"),
|
||||||
String::from("/tmp/thumbs"),
|
String::from("/tmp/thumbs"),
|
||||||
)),
|
)),
|
||||||
|
Data::new(Mutex::new(SqliteTagDao::default())),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(response.status(), 400);
|
assert_eq!(response.status(), 400);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(web::resource("/login").route(web::post().to(login::<SqliteUserDao>)))
|
.service(web::resource("/login").route(web::post().to(login::<SqliteUserDao>)))
|
||||||
.service(web::resource("/photos").route(web::get().to(files::list_photos)))
|
.service(web::resource("/photos").route(web::get().to(files::list_photos::<SqliteTagDao>)))
|
||||||
.service(get_image)
|
.service(get_image)
|
||||||
.service(upload_image)
|
.service(upload_image)
|
||||||
.service(generate_video)
|
.service(generate_video)
|
||||||
|
|||||||
Reference in New Issue
Block a user