Update and Migrate Diesel to 2.0

Almost have tag support working, still figuring out how to get photo
tags.
This commit is contained in:
Cameron Cordes
2023-03-18 14:43:41 -04:00
parent 40c79d13db
commit 68bfcbf85f
8 changed files with 1009 additions and 586 deletions

View File

@@ -4,6 +4,7 @@ extern crate rayon;
use actix_web::web::Data;
use actix_web_prom::PrometheusMetricsBuilder;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use futures::stream::StreamExt;
use lazy_static::lazy_static;
use prometheus::{self, IntGauge};
@@ -14,6 +15,8 @@ use std::{
io::ErrorKind,
path::{Path, PathBuf},
};
use std::error::Error;
use std::sync::Mutex;
use walkdir::{DirEntry, WalkDir};
use actix_files::NamedFile;
@@ -23,6 +26,7 @@ use actix_web::{
web::{self, BufMut, BytesMut},
App, HttpRequest, HttpResponse, HttpServer, Responder,
};
use diesel::sqlite::Sqlite;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use rayon::prelude::*;
@@ -39,6 +43,7 @@ use crate::video::*;
mod auth;
mod data;
mod database;
mod error;
mod files;
mod state;
mod tags;
@@ -60,12 +65,14 @@ lazy_static! {
.unwrap();
}
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
#[get("/image")]
async fn get_image(
_claims: Claims,
request: HttpRequest,
req: web::Query<ThumbnailRequest>,
app_state: web::Data<AppState>,
app_state: Data<AppState>,
) -> impl Responder {
if let Some(path) = is_valid_full_path(&app_state.base_path, &req.path) {
if req.size.is_some() {
@@ -97,7 +104,7 @@ async fn get_image(
async fn get_file_metadata(
_: Claims,
path: web::Query<ThumbnailRequest>,
app_state: web::Data<AppState>,
app_state: Data<AppState>,
) -> impl Responder {
match is_valid_full_path(&app_state.base_path, &path.path)
.ok_or_else(|| ErrorKind::InvalidData.into())
@@ -119,7 +126,7 @@ async fn get_file_metadata(
async fn upload_image(
_: Claims,
mut payload: mp::Multipart,
app_state: web::Data<AppState>,
app_state: Data<AppState>,
) -> impl Responder {
let mut file_content: BytesMut = BytesMut::new();
let mut file_name: Option<String> = None;
@@ -148,7 +155,7 @@ async fn upload_image(
if !file_content.is_empty() {
let full_path = PathBuf::from(&path).join(file_name.unwrap());
if let Some(full_path) =
is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or(""))
is_valid_full_path(&app_state.base_path, full_path.to_str().unwrap_or(""))
{
if !full_path.is_file() && is_image_or_video(&full_path) {
let mut file = File::create(full_path).unwrap();
@@ -170,7 +177,7 @@ async fn upload_image(
#[post("/video/generate")]
async fn generate_video(
_claims: Claims,
app_state: web::Data<AppState>,
app_state: Data<AppState>,
body: web::Json<ThumbnailRequest>,
) -> impl Responder {
let filename = PathBuf::from(&body.path);
@@ -200,7 +207,7 @@ async fn stream_video(
request: HttpRequest,
_: Claims,
path: web::Query<ThumbnailRequest>,
app_state: web::Data<AppState>,
app_state: Data<AppState>,
) -> impl Responder {
let playlist = &path.path;
debug!("Playlist: {}", playlist);
@@ -235,9 +242,10 @@ async fn get_video_part(
#[get("image/favorites")]
async fn favorites(
claims: Claims,
favorites_dao: web::Data<Box<dyn FavoriteDao>>,
favorites_dao: Data<Mutex<Box<dyn FavoriteDao>>>,
) -> impl Responder {
match web::block(move || favorites_dao.get_favorites(claims.sub.parse::<i32>().unwrap())).await
match web::block(move || favorites_dao.lock()
.expect("Unable to get FavoritesDao").get_favorites(claims.sub.parse::<i32>().unwrap())).await
{
Ok(Ok(favorites)) => {
let favorites = favorites
@@ -262,14 +270,14 @@ async fn favorites(
async fn put_add_favorite(
claims: Claims,
body: web::Json<AddFavoriteRequest>,
favorites_dao: web::Data<Box<dyn FavoriteDao>>,
favorites_dao: Data<Mutex<Box<dyn FavoriteDao>>>,
) -> impl Responder {
if let Ok(user_id) = claims.sub.parse::<i32>() {
let path = body.path.clone();
match web::block::<_, Result<usize, DbError>>(move || {
favorites_dao.add_favorite(user_id, &path)
favorites_dao.lock().expect("Unable to get FavoritesDao").add_favorite(user_id, &path)
})
.await
.await
{
Ok(Err(e)) if e.kind == DbErrorKind::AlreadyExists => {
debug!("Favorite: {} exists for user: {}", &body.path, user_id);
@@ -298,15 +306,15 @@ async fn put_add_favorite(
async fn delete_favorite(
claims: Claims,
body: web::Query<AddFavoriteRequest>,
favorites_dao: web::Data<Box<dyn FavoriteDao>>,
favorites_dao: Data<Mutex<Box<dyn FavoriteDao>>>,
) -> impl Responder {
if let Ok(user_id) = claims.sub.parse::<i32>() {
let path = body.path.clone();
web::block(move || {
favorites_dao.remove_favorite(user_id, path);
favorites_dao.lock().expect("Unable to get favorites dao").remove_favorite(user_id, path);
})
.await
.unwrap();
.await
.unwrap();
info!(
"Removing favorite \"{}\" for userid: {}",
@@ -325,7 +333,7 @@ fn create_thumbnails() {
let images = PathBuf::from(dotenv::var("BASE_PATH").unwrap());
walkdir::WalkDir::new(&images)
WalkDir::new(&images)
.into_iter()
.collect::<Vec<Result<_, _>>>()
.into_par_iter()
@@ -422,12 +430,15 @@ fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
env_logger::init();
run_migrations(&mut connect())
.expect("Failed to run migrations");
create_thumbnails();
watch_files();
let system = actix::System::new();
system.block_on(async {
let app_data = web::Data::new(AppState::default());
let app_data = Data::new(AppState::default());
let labels = HashMap::new();
let prometheus = PrometheusMetricsBuilder::new("api")
@@ -447,6 +458,7 @@ fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let user_dao = SqliteUserDao::new();
let favorites_dao = SqliteFavoriteDao::new();
let tag_dao = SqliteTagDao::default();
App::new()
.wrap(middleware::Logger::default())
.service(web::resource("/login").route(web::post().to(login::<SqliteUserDao>)))
@@ -460,21 +472,32 @@ fn main() -> std::io::Result<()> {
.service(put_add_favorite)
.service(delete_favorite)
.service(get_file_metadata)
.service(add_tag)
.service(get_tags)
.service(remove_tagged_photo)
.service(
web::resource("image/tags")
.route(web::post().to(add_tag::<SqliteTagDao>))
.route(web::get().to(get_all_tags::<SqliteTagDao>))
.route(web::get().to(get_tags::<SqliteTagDao>))
.route(web::delete().to(remove_tagged_photo::<SqliteTagDao>)),
)
.app_data(app_data.clone())
.app_data::<Data<SqliteUserDao>>(Data::new(user_dao))
.app_data::<Data<Box<dyn FavoriteDao>>>(Data::new(Box::new(favorites_dao)))
.app_data::<Data<Mutex<SqliteUserDao>>>(Data::new(Mutex::new(user_dao)))
.app_data::<Data<Mutex<Box<dyn FavoriteDao>>>>(Data::new(Mutex::new(Box::new(favorites_dao))))
.app_data::<Data<Mutex<SqliteTagDao>>>(Data::new(Mutex::new(tag_dao)))
.wrap(prometheus.clone())
})
.bind(dotenv::var("BIND_URL").unwrap())?
.bind("localhost:8088")?
.run()
.await
.bind(dotenv::var("BIND_URL").unwrap())?
.bind("localhost:8088")?
.run()
.await
})
}
fn run_migrations(connection: &mut impl MigrationHarness<Sqlite>) -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
connection.run_pending_migrations(MIGRATIONS)?;
Ok(())
}
fn watch_files() {
std::thread::spawn(|| {
let (wtx, wrx) = channel();
@@ -492,10 +515,10 @@ fn watch_files() {
let image_base_path = PathBuf::from(env::var("BASE_PATH").unwrap());
let image_relative = orig.strip_prefix(&image_base_path).unwrap();
if let Ok(old_thumbnail) =
env::var("THUMBNAILS").map(PathBuf::from).map(|mut base| {
base.push(image_relative);
base
})
env::var("THUMBNAILS").map(PathBuf::from).map(|mut base| {
base.push(image_relative);
base
})
{
if let Err(e) = std::fs::remove_file(&old_thumbnail) {
error!(