feature/rust-2024-edition #41
@@ -2,7 +2,7 @@
|
|||||||
name = "image-api"
|
name = "image-api"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
authors = ["Cameron Cordes <cameronc.dev@gmail.com>"]
|
authors = ["Cameron Cordes <cameronc.dev@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
use actix_web::Responder;
|
use actix_web::Responder;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
web::{self, Json},
|
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
|
web::{self, Json},
|
||||||
};
|
};
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
use jsonwebtoken::{EncodingKey, Header, encode};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{secret_key, Claims, CreateAccountRequest, LoginRequest, Token},
|
data::{Claims, CreateAccountRequest, LoginRequest, Token, secret_key},
|
||||||
database::UserDao,
|
database::UserDao,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use std::{fs, str::FromStr};
|
use std::{fs, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{Context, anyhow};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use actix_web::error::ErrorUnauthorized;
|
use actix_web::error::ErrorUnauthorized;
|
||||||
use actix_web::{dev, http::header, Error, FromRequest, HttpRequest};
|
use actix_web::{Error, FromRequest, HttpRequest, dev, http::header};
|
||||||
use futures::future::{err, ok, Ready};
|
use futures::future::{Ready, err, ok};
|
||||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -226,7 +226,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_expired_token() {
|
fn test_expired_token() {
|
||||||
let err = Claims::from_str(
|
let err = Claims::from_str(
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5IiwiZXhwIjoxNn0.eZnfaNfiD54VMbphIqeBICeG9SzAtwNXntLwtTBihjY");
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5IiwiZXhwIjoxNn0.eZnfaNfiD54VMbphIqeBICeG9SzAtwNXntLwtTBihjY",
|
||||||
|
);
|
||||||
|
|
||||||
match err.unwrap_err().into_kind() {
|
match err.unwrap_err().into_kind() {
|
||||||
ErrorKind::ExpiredSignature => assert!(true),
|
ErrorKind::ExpiredSignature => assert!(true),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
use bcrypt::{DEFAULT_COST, hash, verify};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
@@ -30,7 +30,7 @@ impl SqliteUserDao {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use diesel::{Connection, SqliteConnection};
|
use diesel::{Connection, SqliteConnection};
|
||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
|
|
||||||
const DB_MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
const DB_MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
|
|||||||
222
src/files.rs
222
src/files.rs
@@ -7,18 +7,18 @@ use std::sync::Mutex;
|
|||||||
|
|
||||||
use ::anyhow;
|
use ::anyhow;
|
||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{Context, anyhow};
|
||||||
|
|
||||||
use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse, SortType};
|
use crate::data::{Claims, FilesRequest, FilterMode, PhotosResponse, SortType};
|
||||||
use crate::{create_thumbnails, AppState};
|
use crate::{AppState, create_thumbnails};
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
web::{self, Query},
|
|
||||||
HttpRequest, HttpResponse,
|
HttpRequest, HttpResponse,
|
||||||
|
web::{self, Query},
|
||||||
};
|
};
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
|
||||||
use opentelemetry::KeyValue;
|
use opentelemetry::KeyValue;
|
||||||
|
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
||||||
|
|
||||||
use crate::data::SortType::NameAsc;
|
use crate::data::SortType::NameAsc;
|
||||||
use crate::error::IntoHttpError;
|
use crate::error::IntoHttpError;
|
||||||
@@ -144,103 +144,108 @@ pub async fn list_photos<TagD: TagDao, FS: FileSystemAccess>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(files) = file_system.get_files_for_path(search_path) {
|
match file_system.get_files_for_path(search_path) {
|
||||||
info!("Found {:?} files in path: {:?}", files.len(), search_path);
|
Ok(files) => {
|
||||||
|
info!("Found {:?} files in path: {:?}", files.len(), search_path);
|
||||||
|
|
||||||
let photos = files
|
let photos = files
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&f| {
|
.filter(|&f| {
|
||||||
f.metadata().map_or_else(
|
f.metadata().map_or_else(
|
||||||
|e| {
|
|e| {
|
||||||
error!("Failed getting file metadata: {:?}", e);
|
error!("Failed getting file metadata: {:?}", e);
|
||||||
f.extension().is_some()
|
f.extension().is_some()
|
||||||
},
|
},
|
||||||
|md| md.is_file(),
|
|md| md.is_file(),
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
.map(|path: &PathBuf| {
|
||||||
|
let relative = path.strip_prefix(&app_state.base_path).unwrap();
|
||||||
|
relative.to_path_buf()
|
||||||
|
})
|
||||||
|
.map(|f| f.to_str().unwrap().to_string())
|
||||||
|
.map(|file_name| {
|
||||||
|
let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao");
|
||||||
|
let file_tags = tag_dao
|
||||||
|
.get_tags_for_path(&span_context, &file_name)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
(file_name, file_tags)
|
||||||
|
})
|
||||||
|
.filter(|(_, file_tags)| {
|
||||||
|
if let Some(tag_ids) = &req.tag_ids {
|
||||||
|
let tag_ids = tag_ids
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|t| t.parse().ok())
|
||||||
|
.collect::<Vec<i32>>();
|
||||||
|
|
||||||
|
let excluded_tag_ids = &req
|
||||||
|
.exclude_tag_ids
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split(',')
|
||||||
|
.filter_map(|t| t.parse().ok())
|
||||||
|
.collect::<Vec<i32>>();
|
||||||
|
|
||||||
|
let filter_mode = &req.tag_filter_mode.unwrap_or(FilterMode::Any);
|
||||||
|
let excluded = file_tags.iter().any(|t| excluded_tag_ids.contains(&t.id));
|
||||||
|
|
||||||
|
return !excluded
|
||||||
|
&& match filter_mode {
|
||||||
|
FilterMode::Any => {
|
||||||
|
file_tags.iter().any(|t| tag_ids.contains(&t.id))
|
||||||
|
}
|
||||||
|
FilterMode::All => tag_ids
|
||||||
|
.iter()
|
||||||
|
.all(|id| file_tags.iter().any(|tag| &tag.id == id)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.map(|(file_name, tags)| FileWithTagCount {
|
||||||
|
file_name,
|
||||||
|
tag_count: tags.len() as i64,
|
||||||
|
})
|
||||||
|
.collect::<Vec<FileWithTagCount>>();
|
||||||
|
|
||||||
|
let mut response_files = photos
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| f.file_name)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
if let Some(sort_type) = req.sort {
|
||||||
|
debug!("Sorting files: {:?}", sort_type);
|
||||||
|
response_files = sort(photos, sort_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dirs = files
|
||||||
|
.iter()
|
||||||
|
.filter(|&f| f.metadata().map_or(false, |md| md.is_dir()))
|
||||||
|
.map(|path: &PathBuf| {
|
||||||
|
let relative = path.strip_prefix(&app_state.base_path).unwrap();
|
||||||
|
relative.to_path_buf()
|
||||||
|
})
|
||||||
|
.map(|f| f.to_str().unwrap().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
span_context
|
||||||
|
.span()
|
||||||
|
.set_attribute(KeyValue::new("file_count", files.len().to_string()));
|
||||||
|
span_context.span().set_status(Status::Ok);
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(PhotosResponse {
|
||||||
|
photos: response_files,
|
||||||
|
dirs,
|
||||||
})
|
})
|
||||||
.map(|path: &PathBuf| {
|
|
||||||
let relative = path.strip_prefix(&app_state.base_path).unwrap();
|
|
||||||
relative.to_path_buf()
|
|
||||||
})
|
|
||||||
.map(|f| f.to_str().unwrap().to_string())
|
|
||||||
.map(|file_name| {
|
|
||||||
let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao");
|
|
||||||
let file_tags = tag_dao
|
|
||||||
.get_tags_for_path(&span_context, &file_name)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
(file_name, file_tags)
|
|
||||||
})
|
|
||||||
.filter(|(_, file_tags)| {
|
|
||||||
if let Some(tag_ids) = &req.tag_ids {
|
|
||||||
let tag_ids = tag_ids
|
|
||||||
.split(',')
|
|
||||||
.filter_map(|t| t.parse().ok())
|
|
||||||
.collect::<Vec<i32>>();
|
|
||||||
|
|
||||||
let excluded_tag_ids = &req
|
|
||||||
.exclude_tag_ids
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.split(',')
|
|
||||||
.filter_map(|t| t.parse().ok())
|
|
||||||
.collect::<Vec<i32>>();
|
|
||||||
|
|
||||||
let filter_mode = &req.tag_filter_mode.unwrap_or(FilterMode::Any);
|
|
||||||
let excluded = file_tags.iter().any(|t| excluded_tag_ids.contains(&t.id));
|
|
||||||
|
|
||||||
return !excluded
|
|
||||||
&& match filter_mode {
|
|
||||||
FilterMode::Any => file_tags.iter().any(|t| tag_ids.contains(&t.id)),
|
|
||||||
FilterMode::All => tag_ids
|
|
||||||
.iter()
|
|
||||||
.all(|id| file_tags.iter().any(|tag| &tag.id == id)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
})
|
|
||||||
.map(|(file_name, tags)| FileWithTagCount {
|
|
||||||
file_name,
|
|
||||||
tag_count: tags.len() as i64,
|
|
||||||
})
|
|
||||||
.collect::<Vec<FileWithTagCount>>();
|
|
||||||
|
|
||||||
let mut response_files = photos
|
|
||||||
.clone()
|
|
||||||
.into_iter()
|
|
||||||
.map(|f| f.file_name)
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
if let Some(sort_type) = req.sort {
|
|
||||||
debug!("Sorting files: {:?}", sort_type);
|
|
||||||
response_files = sort(photos, sort_type)
|
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
let dirs = files
|
error!("Bad photos request: {}", req.path);
|
||||||
.iter()
|
span_context
|
||||||
.filter(|&f| f.metadata().map_or(false, |md| md.is_dir()))
|
.span()
|
||||||
.map(|path: &PathBuf| {
|
.set_status(Status::error("Invalid path"));
|
||||||
let relative = path.strip_prefix(&app_state.base_path).unwrap();
|
HttpResponse::BadRequest().finish()
|
||||||
relative.to_path_buf()
|
}
|
||||||
})
|
|
||||||
.map(|f| f.to_str().unwrap().to_string())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
span_context
|
|
||||||
.span()
|
|
||||||
.set_attribute(KeyValue::new("file_count", files.len().to_string()));
|
|
||||||
span_context.span().set_status(Status::Ok);
|
|
||||||
|
|
||||||
HttpResponse::Ok().json(PhotosResponse {
|
|
||||||
photos: response_files,
|
|
||||||
dirs,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
error!("Bad photos request: {}", req.path);
|
|
||||||
span_context
|
|
||||||
.span()
|
|
||||||
.set_status(Status::error("Invalid path"));
|
|
||||||
HttpResponse::BadRequest().finish()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,12 +510,12 @@ mod tests {
|
|||||||
|
|
||||||
mod api {
|
mod api {
|
||||||
use super::*;
|
use super::*;
|
||||||
use actix_web::{web::Query, HttpResponse};
|
use actix_web::{HttpResponse, web::Query};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
AppState,
|
||||||
data::{Claims, PhotosResponse},
|
data::{Claims, PhotosResponse},
|
||||||
testhelpers::BodyReader,
|
testhelpers::BodyReader,
|
||||||
AppState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::database::test::in_memory_db_connection;
|
use crate::database::test::in_memory_db_connection;
|
||||||
@@ -561,14 +566,15 @@ mod tests {
|
|||||||
|
|
||||||
assert!(body.photos.contains(&String::from("photo.jpg")));
|
assert!(body.photos.contains(&String::from("photo.jpg")));
|
||||||
assert!(body.dirs.contains(&String::from("test-dir")));
|
assert!(body.dirs.contains(&String::from("test-dir")));
|
||||||
assert!(body
|
assert!(
|
||||||
.photos
|
body.photos
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|filename| !filename.ends_with(".png")
|
.filter(|filename| !filename.ends_with(".png")
|
||||||
&& !filename.ends_with(".jpg")
|
&& !filename.ends_with(".jpg")
|
||||||
&& !filename.ends_with(".jpeg"))
|
&& !filename.ends_with(".jpeg"))
|
||||||
.collect::<Vec<&String>>()
|
.collect::<Vec<&String>>()
|
||||||
.is_empty());
|
.is_empty()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|||||||
51
src/main.rs
51
src/main.rs
@@ -4,13 +4,13 @@ extern crate rayon;
|
|||||||
|
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web_prom::PrometheusMetricsBuilder;
|
use actix_web_prom::PrometheusMetricsBuilder;
|
||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{self, IntGauge};
|
use prometheus::{self, IntGauge};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::mpsc::channel;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
use std::{collections::HashMap, io::prelude::*};
|
use std::{collections::HashMap, io::prelude::*};
|
||||||
use std::{env, fs::File};
|
use std::{env, fs::File};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -22,9 +22,8 @@ use walkdir::{DirEntry, WalkDir};
|
|||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_multipart as mp;
|
use actix_multipart as mp;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
delete, get, middleware, post, put,
|
App, HttpRequest, HttpResponse, HttpServer, Responder, delete, get, middleware, post, put,
|
||||||
web::{self, BufMut, BytesMut},
|
web::{self, BufMut, BytesMut},
|
||||||
App, HttpRequest, HttpResponse, HttpServer, Responder,
|
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@@ -36,19 +35,19 @@ use crate::auth::login;
|
|||||||
use crate::data::*;
|
use crate::data::*;
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::files::{
|
use crate::files::{
|
||||||
is_image_or_video, is_valid_full_path, move_file, RealFileSystem, RefreshThumbnailsMessage,
|
RealFileSystem, RefreshThumbnailsMessage, is_image_or_video, is_valid_full_path, move_file,
|
||||||
};
|
};
|
||||||
use crate::otel::{extract_context_from_request, global_tracer};
|
use crate::otel::{extract_context_from_request, global_tracer};
|
||||||
use crate::service::ServiceBuilder;
|
use crate::service::ServiceBuilder;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::tags::*;
|
use crate::tags::*;
|
||||||
use crate::video::actors::{
|
use crate::video::actors::{
|
||||||
create_playlist, generate_video_thumbnail, ProcessMessage, ScanDirectoryMessage,
|
ProcessMessage, ScanDirectoryMessage, create_playlist, generate_video_thumbnail,
|
||||||
};
|
};
|
||||||
use crate::video::generate_video_gifs;
|
use crate::video::generate_video_gifs;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
||||||
use opentelemetry::{global, KeyValue};
|
use opentelemetry::{KeyValue, global};
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
mod data;
|
mod data;
|
||||||
@@ -332,12 +331,17 @@ async fn stream_video(
|
|||||||
span.set_status(Status::error(format!("playlist not valid {}", playlist)));
|
span.set_status(Status::error(format!("playlist not valid {}", playlist)));
|
||||||
|
|
||||||
HttpResponse::BadRequest().finish()
|
HttpResponse::BadRequest().finish()
|
||||||
} else if let Ok(file) = NamedFile::open(playlist) {
|
|
||||||
span.set_status(Status::Ok);
|
|
||||||
file.into_response(&request)
|
|
||||||
} else {
|
} else {
|
||||||
span.set_status(Status::error(format!("playlist not found {}", playlist)));
|
match NamedFile::open(playlist) {
|
||||||
HttpResponse::NotFound().finish()
|
Ok(file) => {
|
||||||
|
span.set_status(Status::Ok);
|
||||||
|
file.into_response(&request)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
span.set_status(Status::error(format!("playlist not found {}", playlist)));
|
||||||
|
HttpResponse::NotFound().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,16 +363,19 @@ async fn get_video_part(
|
|||||||
file_part.push(app_state.video_path.clone());
|
file_part.push(app_state.video_path.clone());
|
||||||
file_part.push(part);
|
file_part.push(part);
|
||||||
// TODO: Do we need to guard against directory attacks here?
|
// TODO: Do we need to guard against directory attacks here?
|
||||||
if let Ok(file) = NamedFile::open(&file_part) {
|
match NamedFile::open(&file_part) {
|
||||||
span.set_status(Status::Ok);
|
Ok(file) => {
|
||||||
file.into_response(&request)
|
span.set_status(Status::Ok);
|
||||||
} else {
|
file.into_response(&request)
|
||||||
error!("Video part not found: {:?}", file_part);
|
}
|
||||||
span.set_status(Status::error(format!(
|
_ => {
|
||||||
"Video part not found '{}'",
|
error!("Video part not found: {:?}", file_part);
|
||||||
file_part.to_str().unwrap()
|
span.set_status(Status::error(format!(
|
||||||
)));
|
"Video part not found '{}'",
|
||||||
HttpResponse::NotFound().finish()
|
file_part.to_str().unwrap()
|
||||||
|
)));
|
||||||
|
HttpResponse::NotFound().finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{get, web, HttpRequest, HttpResponse, Responder};
|
use actix_web::{HttpRequest, HttpResponse, Responder, get, web};
|
||||||
use chrono::LocalResult::{Ambiguous, Single};
|
use chrono::LocalResult::{Ambiguous, Single};
|
||||||
use chrono::{DateTime, Datelike, FixedOffset, Local, LocalResult, NaiveDate, TimeZone, Utc};
|
use chrono::{DateTime, Datelike, FixedOffset, Local, LocalResult, NaiveDate, TimeZone, Utc};
|
||||||
use log::{debug, trace, warn};
|
use log::{debug, trace, warn};
|
||||||
use opentelemetry::trace::{Span, Status, Tracer};
|
|
||||||
use opentelemetry::KeyValue;
|
use opentelemetry::KeyValue;
|
||||||
|
use opentelemetry::trace::{Span, Status, Tracer};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -135,21 +135,19 @@ fn extract_date_from_filename(filename: &str) -> Option<DateTime<FixedOffset>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 1. Screenshot format: Screenshot_2014-06-01-20-44-50.png
|
// 1. Screenshot format: Screenshot_2014-06-01-20-44-50.png
|
||||||
if let Some(captures) =
|
if let Some(captures) = regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})")
|
||||||
regex::Regex::new(r"(\d{4})-(\d{2})-(\d{2})-(\d{2})-(\d{2})-(\d{2})")
|
.ok()?
|
||||||
.ok()?
|
.captures(filename)
|
||||||
.captures(filename)
|
.and_then(|c| build_date_from_ymd_capture(&c))
|
||||||
.and_then(|c| build_date_from_ymd_capture(&c))
|
|
||||||
{
|
{
|
||||||
return Some(captures);
|
return Some(captures);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Screenshot format: Screenshot_20140601[_-]204450.png
|
// Screenshot format: Screenshot_20140601[_-]204450.png
|
||||||
if let Some(captures) =
|
if let Some(captures) = regex::Regex::new(r"(\d{4})(\d{2})(\d{2})[_-](\d{2})(\d{2})(\d{2})")
|
||||||
regex::Regex::new(r"(\d{4})(\d{2})(\d{2})[_-](\d{2})(\d{2})(\d{2})")
|
.ok()?
|
||||||
.ok()?
|
.captures(filename)
|
||||||
.captures(filename)
|
.and_then(|c| build_date_from_ymd_capture(&c))
|
||||||
.and_then(|c| build_date_from_ymd_capture(&c))
|
|
||||||
{
|
{
|
||||||
return Some(captures);
|
return Some(captures);
|
||||||
}
|
}
|
||||||
@@ -468,7 +466,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_extract_date_from_filename_timestamp_format() {
|
fn test_extract_date_from_filename_timestamp_format() {
|
||||||
let filename = "xyz_1401638400.jpeg"; // Unix timestamp for 2014-06-01 16:00:00 UTC
|
let filename = "xyz_1401638400.jpeg"; // Unix timestamp for 2014-06-01 16:00:00 UTC
|
||||||
// Timestamps are already in UTC, so timezone doesn't matter for this test
|
// Timestamps are already in UTC, so timezone doesn't matter for this test
|
||||||
let date_time = extract_date_from_filename(filename).unwrap();
|
let date_time = extract_date_from_filename(filename).unwrap();
|
||||||
|
|
||||||
assert_eq!(date_time.year(), 2014);
|
assert_eq!(date_time.year(), 2014);
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
use actix_web::http::header::HeaderMap;
|
|
||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
|
use actix_web::http::header::HeaderMap;
|
||||||
use opentelemetry::global::{BoxedSpan, BoxedTracer};
|
use opentelemetry::global::{BoxedSpan, BoxedTracer};
|
||||||
use opentelemetry::propagation::TextMapPropagator;
|
use opentelemetry::propagation::TextMapPropagator;
|
||||||
use opentelemetry::trace::{Span, Status, Tracer};
|
use opentelemetry::trace::{Span, Status, Tracer};
|
||||||
use opentelemetry::{global, Context, KeyValue};
|
use opentelemetry::{Context, KeyValue, global};
|
||||||
use opentelemetry_appender_log::OpenTelemetryLogBridge;
|
use opentelemetry_appender_log::OpenTelemetryLogBridge;
|
||||||
use opentelemetry_otlp::WithExportConfig;
|
use opentelemetry_otlp::WithExportConfig;
|
||||||
|
use opentelemetry_sdk::Resource;
|
||||||
use opentelemetry_sdk::logs::{BatchLogProcessor, SdkLoggerProvider};
|
use opentelemetry_sdk::logs::{BatchLogProcessor, SdkLoggerProvider};
|
||||||
use opentelemetry_sdk::propagation::TraceContextPropagator;
|
use opentelemetry_sdk::propagation::TraceContextPropagator;
|
||||||
use opentelemetry_sdk::Resource;
|
|
||||||
|
|
||||||
pub fn global_tracer() -> BoxedTracer {
|
pub fn global_tracer() -> BoxedTracer {
|
||||||
global::tracer("image-server")
|
global::tracer("image-server")
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
use crate::data::GetTagsRequest;
|
use crate::data::GetTagsRequest;
|
||||||
use crate::otel::{extract_context_from_request, global_tracer, trace_db_call};
|
use crate::otel::{extract_context_from_request, global_tracer, trace_db_call};
|
||||||
use crate::{connect, data::AddTagRequest, error::IntoHttpError, schema, Claims, ThumbnailRequest};
|
use crate::{Claims, ThumbnailRequest, connect, data::AddTagRequest, error::IntoHttpError, schema};
|
||||||
use actix_web::dev::{ServiceFactory, ServiceRequest};
|
use actix_web::dev::{ServiceFactory, ServiceRequest};
|
||||||
use actix_web::{web, App, HttpRequest, HttpResponse, Responder};
|
use actix_web::{App, HttpRequest, HttpResponse, Responder, web};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::dsl::count_star;
|
use diesel::dsl::count_star;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
|
||||||
use opentelemetry::KeyValue;
|
use opentelemetry::KeyValue;
|
||||||
|
use opentelemetry::trace::{Span, Status, TraceContextExt, Tracer};
|
||||||
use schema::{tagged_photo, tags};
|
use schema::{tagged_photo, tags};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::{BoxBody, MessageBody},
|
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
|
body::{BoxBody, MessageBody},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::database::{models::User, UserDao};
|
use crate::database::{UserDao, models::User};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::otel::global_tracer;
|
|||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
use log::{debug, error, info, trace, warn};
|
use log::{debug, error, info, trace, warn};
|
||||||
use opentelemetry::trace::{Span, Status, Tracer};
|
|
||||||
use opentelemetry::KeyValue;
|
use opentelemetry::KeyValue;
|
||||||
|
use opentelemetry::trace::{Span, Status, Tracer};
|
||||||
use std::io::Result;
|
use std::io::Result;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Child, Command, ExitStatus, Stdio};
|
use std::process::{Child, Command, ExitStatus, Stdio};
|
||||||
|
|||||||
Reference in New Issue
Block a user