From 03dd6a54659f02f6d6ccfdf8377e514313b81e38 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sat, 30 Apr 2022 16:52:15 +0200 Subject: [PATCH] Use diesel 2.0 Most of the changes are for database access now requiring an &mut connection. --- CHANGELOG.md | 3 + Cargo.toml | 4 +- .../2022-04-29-203357_datefuncs/down.sql | 5 + migrations/2022-04-29-203357_datefuncs/up.sql | 16 ++ src/adm/findphotos.rs | 31 ++-- src/adm/makepublic.rs | 14 +- src/adm/precache.rs | 2 +- src/adm/stats.rs | 4 +- src/adm/users.rs | 4 +- src/fetch_places.rs | 22 ++- src/main.rs | 8 +- src/models.rs | 22 +-- src/photosdir.rs | 2 +- src/server/admin.rs | 26 +-- src/server/api.rs | 16 +- src/server/autocomplete.rs | 17 +- src/server/image.rs | 2 +- src/server/login.rs | 6 +- src/server/mod.rs | 8 +- src/server/search.rs | 17 +- src/server/splitlist.rs | 21 +-- src/server/views_by_category.rs | 20 ++- src/server/views_by_date.rs | 155 +++++++++++------- 23 files changed, 252 insertions(+), 173 deletions(-) create mode 100644 migrations/2022-04-29-203357_datefuncs/down.sql create mode 100644 migrations/2022-04-29-203357_datefuncs/up.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 34facf1..f5a5faf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ The format is based on * Improved logging by using tracing and tracing-subscriber rather than log and env_logger. * Four more kinds of OSM areas to recognize. +* Update diesel to 2.0.0: Mainly most operations now needs a `&mut + PgConnection`. Also, getting parts of dates are now done by sql + functions. * Update ructe to 0.15.0. * Add this changelog. diff --git a/Cargo.toml b/Cargo.toml index 617b44b..be939c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ ructe = { version = "0.15.0", features = ["sass", "warp03"] } [dependencies] brotli = "3.3.0" -chrono = "0.4.0" # Must match version used by diesel +chrono = "0.4.19" # Must match version used by diesel clap = { version = "3.2.8", features = ["derive", "terminal_size", "wrap_help", "env"] } dotenv = "0.15" flate2 = "1.0.14" @@ -39,7 +39,7 @@ version = "1.1.1" [dependencies.diesel] default-features = false features = ["r2d2", "chrono", "postgres"] -version = "1.4.0" +version = "2.0.0" [dependencies.warp] default-features = false diff --git a/migrations/2022-04-29-203357_datefuncs/down.sql b/migrations/2022-04-29-203357_datefuncs/down.sql new file mode 100644 index 0000000..1767953 --- /dev/null +++ b/migrations/2022-04-29-203357_datefuncs/down.sql @@ -0,0 +1,5 @@ +-- This file should undo anything in `up.sql` + +drop function year_of_timestamp; +drop function month_of_timestamp; +drop function day_of_timestamp; diff --git a/migrations/2022-04-29-203357_datefuncs/up.sql b/migrations/2022-04-29-203357_datefuncs/up.sql new file mode 100644 index 0000000..fd5b76b --- /dev/null +++ b/migrations/2022-04-29-203357_datefuncs/up.sql @@ -0,0 +1,16 @@ +-- SQL functions for handling dates + +create function year_of_timestamp(arg timestamp with time zone) + returns smallint + language sql immutable strict parallel safe + as $func$ select cast(date_part('year', arg at time zone 'UTC') as smallint); $func$; + +create function month_of_timestamp(arg timestamp with time zone) + returns smallint + language sql immutable strict parallel safe + as $func$ select cast(date_part('month', arg at time zone 'UTC') as smallint); $func$; + +create function day_of_timestamp(arg timestamp with time zone) + returns smallint + language sql immutable strict parallel safe + as $func$ select cast(date_part('day', arg at time zone 'UTC') as smallint); $func$; diff --git a/src/adm/findphotos.rs b/src/adm/findphotos.rs index 646a38a..355c7e3 100644 --- a/src/adm/findphotos.rs +++ b/src/adm/findphotos.rs @@ -23,15 +23,15 @@ pub struct Findphotos { impl Findphotos { pub fn run(&self) -> Result<(), Error> { let pd = PhotosDir::new(&self.photos.photos_dir); - let db = self.db.connect()?; + let mut db = self.db.connect()?; if !self.base.is_empty() { for base in &self.base { - crawl(&db, &pd, Path::new(base)).map_err(|e| { + crawl(&mut db, &pd, Path::new(base)).map_err(|e| { Error::Other(format!("Failed to crawl {}: {}", base, e)) })?; } } else { - crawl(&db, &pd, Path::new("")).map_err(|e| { + crawl(&mut db, &pd, Path::new("")).map_err(|e| { Error::Other(format!("Failed to crawl: {}", e)) })?; } @@ -40,35 +40,36 @@ impl Findphotos { } fn crawl( - db: &PgConnection, + db: &mut PgConnection, photos: &PhotosDir, only_in: &Path, ) -> Result<(), Error> { - photos.find_files( - only_in, - &|path, exif| match save_photo(db, path, exif) { - Ok(()) => debug!("Saved photo {}", path), - Err(e) => warn!("Failed to save photo {}: {:?}", path, e), - }, - )?; + photos.find_files(only_in, &mut |path, exif| match save_photo( + db, path, exif, + ) { + Ok(()) => debug!("Saved photo {}", path), + Err(e) => warn!("Failed to save photo {}: {:?}", path, e), + })?; Ok(()) } fn save_photo( - db: &PgConnection, + db: &mut PgConnection, file_path: &str, exif: &ExifData, ) -> Result<(), Error> { let width = exif.width.ok_or(Error::MissingWidth)?; let height = exif.height.ok_or(Error::MissingHeight)?; + let rot = exif.rotation()?; + let cam = find_camera(db, exif)?; let photo = match Photo::create_or_set_basics( db, file_path, width as i32, height as i32, exif.date(), - exif.rotation()?, - find_camera(db, exif)?, + rot, + cam, )? { Modification::Created(photo) => { info!("Created #{}, {}", photo.id, photo.path); @@ -116,7 +117,7 @@ fn save_photo( } fn find_camera( - db: &PgConnection, + db: &mut PgConnection, exif: &ExifData, ) -> Result, Error> { if let Some((make, model)) = exif.camera() { diff --git a/src/adm/makepublic.rs b/src/adm/makepublic.rs index 50dd026..ff84262 100644 --- a/src/adm/makepublic.rs +++ b/src/adm/makepublic.rs @@ -30,7 +30,7 @@ pub struct Makepublic { impl Makepublic { pub fn run(&self) -> Result<(), Error> { - let db = self.db.connect()?; + let mut db = self.db.connect()?; match ( self.list.as_ref().map(AsRef::as_ref), &self.tag, @@ -38,12 +38,12 @@ impl Makepublic { ) { (Some("-"), None, None) => { let list = io::stdin(); - by_file_list(&db, list.lock())?; + by_file_list(&mut db, list.lock())?; Ok(()) } (Some(list), None, None) => { let list = BufReader::new(File::open(list)?); - by_file_list(&db, list) + by_file_list(&mut db, list) } (None, Some(tag), None) => { use crate::schema::photo_tags::dsl as pt; @@ -60,17 +60,17 @@ impl Makepublic { ), ) .set(p::is_public.eq(true)) - .execute(&db)?; + .execute(&mut db)?; println!("Made {} images public.", n); Ok(()) } - (None, None, Some(image)) => one(&db, image), + (None, None, Some(image)) => one(&mut db, image), _ => Err(Error::Other("bad command".to_string())), } } } -pub fn one(db: &PgConnection, tpath: &str) -> Result<(), Error> { +pub fn one(db: &mut PgConnection, tpath: &str) -> Result<(), Error> { use crate::schema::photos::dsl::*; match update(photos.filter(path.eq(&tpath))) .set(is_public.eq(true)) @@ -88,7 +88,7 @@ pub fn one(db: &PgConnection, tpath: &str) -> Result<(), Error> { } pub fn by_file_list( - db: &PgConnection, + db: &mut PgConnection, list: In, ) -> Result<(), Error> { for line in list.lines() { diff --git a/src/adm/precache.rs b/src/adm/precache.rs index 411c14e..4de436a 100644 --- a/src/adm/precache.rs +++ b/src/adm/precache.rs @@ -37,7 +37,7 @@ impl Args { let (mut n, mut n_stored) = (0, 0); let photos = Photo::query(true) .order((is_public.desc(), date.desc().nulls_last())) - .load::(&self.db.connect()?)?; + .load::(&mut self.db.connect()?)?; let no_expire = 0; let pd = PhotosDir::new(&self.photos.photos_dir); for photo in photos { diff --git a/src/adm/stats.rs b/src/adm/stats.rs index b904b44..992fdf6 100644 --- a/src/adm/stats.rs +++ b/src/adm/stats.rs @@ -3,12 +3,12 @@ use crate::schema::people::dsl::people; use crate::schema::photos::dsl::photos; use crate::schema::places::dsl::places; use crate::schema::tags::dsl::tags; -use diesel::expression::dsl::{count_star, sql}; +use diesel::dsl::{count_star, sql}; use diesel::pg::PgConnection; use diesel::prelude::*; use diesel::sql_types::{BigInt, Double, Nullable}; -pub fn show_stats(db: &PgConnection) -> Result<(), Error> { +pub fn show_stats(db: &mut PgConnection) -> Result<(), Error> { println!( "There are {} photos in total.", photos.select(count_star()).first::(db)?, diff --git a/src/adm/users.rs b/src/adm/users.rs index 6e04f9e..9a930b8 100644 --- a/src/adm/users.rs +++ b/src/adm/users.rs @@ -6,7 +6,7 @@ use djangohashers::make_password; use rand::{thread_rng, Rng}; use std::iter::Iterator; -pub fn list(db: &PgConnection) -> Result<(), Error> { +pub fn list(db: &mut PgConnection) -> Result<(), Error> { use crate::schema::users::dsl::*; println!( "Existing users: {:?}.", @@ -15,7 +15,7 @@ pub fn list(db: &PgConnection) -> Result<(), Error> { Ok(()) } -pub fn passwd(db: &PgConnection, uname: &str) -> Result<(), Error> { +pub fn passwd(db: &mut PgConnection, uname: &str) -> Result<(), Error> { let pword = random_password(14); let hashword = make_password(&pword); use crate::schema::users::dsl::*; diff --git a/src/fetch_places.rs b/src/fetch_places.rs index 4db3402..a9ad223 100644 --- a/src/fetch_places.rs +++ b/src/fetch_places.rs @@ -38,7 +38,7 @@ impl Fetchplaces { )) .order(pos::photo_id.desc()) .limit(self.limit) - .load::<(i32, Coord)>(&db.get()?)?; + .load::<(i32, Coord)>(&mut db.get()?)?; for (photo_id, coord) in result { println!("Find places for #{}, {:?}", photo_id, coord); self.overpass.update_image_places(&db, photo_id).await?; @@ -74,7 +74,9 @@ impl OverpassOpt { .filter(photo_id.eq(image)) .select((latitude, longitude)) .first::( - &db.get().map_err(|e| Error::Pool(image, e.to_string()))?, + &mut db + .get() + .map_err(|e| Error::Pool(image, e.to_string()))?, ) .optional() .map_err(|e| Error::Db(image, e))? @@ -96,14 +98,16 @@ impl OverpassOpt { .and_then(|o| o.get("elements")) .and_then(Value::as_array) { - let c = db.get().map_err(|e| Error::Pool(image, e.to_string()))?; + let mut c = + db.get().map_err(|e| Error::Pool(image, e.to_string()))?; for obj in elements { if let (Some(t_osm_id), Some((name, level))) = (osm_id(obj), name_and_level(obj)) { debug!("{}: {} (level {})", t_osm_id, name, level); - let place = get_or_create_place(&c, t_osm_id, name, level) - .map_err(|e| Error::Db(image, e))?; + let place = + get_or_create_place(&mut c, t_osm_id, name, level) + .map_err(|e| Error::Db(image, e))?; if place.osm_id.is_none() { debug!("Matched {:?} by name, update osm info", place); use crate::schema::places::dsl::*; @@ -113,7 +117,7 @@ impl OverpassOpt { osm_id.eq(Some(t_osm_id)), osm_level.eq(level), )) - .execute(&c) + .execute(&mut c) .map_err(|e| Error::Db(image, e))?; } use crate::models::PhotoPlace; @@ -121,7 +125,7 @@ impl OverpassOpt { let q = photo_places .filter(photo_id.eq(image)) .filter(place_id.eq(place.id)); - if q.first::(&c).is_ok() { + if q.first::(&mut c).is_ok() { debug!( "Photo #{} already has {} ({})", image, place.id, place.place_name @@ -132,7 +136,7 @@ impl OverpassOpt { photo_id.eq(image), place_id.eq(place.id), )) - .execute(&c) + .execute(&mut c) .map_err(|e| Error::Db(image, e))?; } } else { @@ -291,7 +295,7 @@ fn tag_str<'a>(tags: &'a Value, name: &str) -> Option<&'a str> { } fn get_or_create_place( - c: &PgConnection, + c: &mut PgConnection, t_osm_id: i64, name: &str, level: i16, diff --git a/src/main.rs b/src/main.rs index 40b8add..c742cd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,9 +102,11 @@ async fn run(args: &RPhotos) -> Result<(), Error> { match args { RPhotos::Findphotos(cmd) => cmd.run(), RPhotos::Makepublic(cmd) => cmd.run(), - RPhotos::Stats(db) => show_stats(&db.connect()?), - RPhotos::Userlist { db } => users::list(&db.connect()?), - RPhotos::Userpass { db, user } => users::passwd(&db.connect()?, user), + RPhotos::Stats(db) => show_stats(&mut db.connect()?), + RPhotos::Userlist { db } => users::list(&mut db.connect()?), + RPhotos::Userpass { db, user } => { + users::passwd(&mut db.connect()?, user) + } RPhotos::Fetchplaces(cmd) => cmd.run().await, RPhotos::Precache(cmd) => cmd.run().await, RPhotos::Storestatics { dir } => storestatics::to_dir(dir), diff --git a/src/models.rs b/src/models.rs index 100346c..4026df1 100644 --- a/src/models.rs +++ b/src/models.rs @@ -28,7 +28,7 @@ pub struct PhotoDetails { pub camera: Option, } impl PhotoDetails { - pub fn load(id: i32, db: &PgConnection) -> Result { + pub fn load(id: i32, db: &mut PgConnection) -> Result { use crate::schema::photos::dsl::photos; let photo = photos.find(id).first::(db)?; let attribution = photo @@ -133,7 +133,7 @@ impl Photo { } pub fn update_by_path( - db: &PgConnection, + db: &mut PgConnection, file_path: &str, newwidth: i32, newheight: i32, @@ -178,7 +178,7 @@ impl Photo { } pub fn create_or_set_basics( - db: &PgConnection, + db: &mut PgConnection, file_path: &str, newwidth: i32, newheight: i32, @@ -239,7 +239,7 @@ impl Photo { } pub trait Facet { - fn by_slug(slug: &str, db: &PgConnection) -> Result + fn by_slug(slug: &str, db: &mut PgConnection) -> Result where Self: Sized; } @@ -252,7 +252,7 @@ pub struct Tag { } impl Facet for Tag { - fn by_slug(slug: &str, db: &PgConnection) -> Result { + fn by_slug(slug: &str, db: &mut PgConnection) -> Result { t::tags.filter(t::slug.eq(slug)).first(db) } } @@ -273,7 +273,7 @@ pub struct Person { impl Person { pub fn get_or_create_name( - db: &PgConnection, + db: &mut PgConnection, name: &str, ) -> Result { h::people @@ -291,7 +291,7 @@ impl Person { } impl Facet for Person { - fn by_slug(slug: &str, db: &PgConnection) -> Result { + fn by_slug(slug: &str, db: &mut PgConnection) -> Result { h::people.filter(h::slug.eq(slug)).first(db) } } @@ -313,7 +313,7 @@ pub struct Place { } impl Facet for Place { - fn by_slug(slug: &str, db: &PgConnection) -> Result { + fn by_slug(slug: &str, db: &mut PgConnection) -> Result { l::places.filter(l::slug.eq(slug)).first(db) } } @@ -334,7 +334,7 @@ pub struct Camera { impl Camera { pub fn get_or_create( - db: &PgConnection, + db: &mut PgConnection, make: &str, modl: &str, ) -> Result { @@ -362,8 +362,8 @@ pub struct Coord { impl Queryable<(Integer, Integer), Pg> for Coord { type Row = (i32, i32); - fn build(row: Self::Row) -> Self { - Coord::from((row.0, row.1)) + fn build(row: Self::Row) -> diesel::deserialize::Result { + Ok(Coord::from((row.0, row.1))) } } diff --git a/src/photosdir.rs b/src/photosdir.rs index 2a49c2a..b696800 100644 --- a/src/photosdir.rs +++ b/src/photosdir.rs @@ -31,7 +31,7 @@ impl PhotosDir { pub fn find_files( &self, dir: &Path, - cb: &dyn Fn(&str, &ExifData), + cb: &mut dyn FnMut(&str, &ExifData), ) -> io::Result<()> { let absdir = self.basedir.join(dir); if fs::metadata(&absdir)?.is_dir() { diff --git a/src/server/admin.rs b/src/server/admin.rs index 6ec2bf5..cebefba 100644 --- a/src/server/admin.rs +++ b/src/server/admin.rs @@ -35,8 +35,8 @@ fn rotate(context: Context, form: RotateForm) -> Result { } info!("Should rotate #{} by {}", form.image, form.angle); use crate::schema::photos::dsl::photos; - let c = context.db()?; - let c: &PgConnection = &c; + let mut c = context.db()?; + let c: &mut PgConnection = &mut c; let mut image = or_404q!(photos.find(form.image).first::(c), context); let newvalue = (360 + image.rotation + form.angle) % 360; @@ -58,19 +58,19 @@ async fn set_tag(context: Context, form: TagForm) -> Result { if !context.is_authorized() { return Err(ViewError::PermissionDenied); } - let c = context.db()?; + let mut c = context.db()?; use crate::models::Tag; let tag = { use crate::schema::tags::dsl::*; tags.filter(tag_name.ilike(&form.tag)) - .first::(&c) + .first::(&mut c) .or_else(|_| { diesel::insert_into(tags) .values(( tag_name.eq(&form.tag), slug.eq(&slugify(&form.tag)), )) - .get_result::(&c) + .get_result::(&mut c) })? }; use crate::schema::photo_tags::dsl::*; @@ -78,13 +78,13 @@ async fn set_tag(context: Context, form: TagForm) -> Result { .filter(photo_id.eq(form.image)) .filter(tag_id.eq(tag.id)) .count(); - if q.get_result::(&c)? > 0 { + if q.get_result::(&mut c)? > 0 { info!("Photo #{} already has {:?}", form.image, form.tag); } else { info!("Add {:?} on photo #{}!", form.tag, form.image); diesel::insert_into(photo_tags) .values((photo_id.eq(form.image), tag_id.eq(tag.id))) - .execute(&c)?; + .execute(&mut c)?; } Ok(redirect_to_img(form.image)) } @@ -99,20 +99,20 @@ async fn set_person(context: Context, form: PersonForm) -> Result { if !context.is_authorized() { return Err(ViewError::PermissionDenied); } - let c = context.db()?; + let mut c = context.db()?; use crate::models::{Person, PhotoPerson}; - let person = Person::get_or_create_name(&c, &form.person)?; + let person = Person::get_or_create_name(&mut c, &form.person)?; use crate::schema::photo_people::dsl::*; let q = photo_people .filter(photo_id.eq(form.image)) .filter(person_id.eq(person.id)); - if q.first::(&c).optional()?.is_some() { + if q.first::(&mut c).optional()?.is_some() { info!("Photo #{} already has {:?}", form.image, person); } else { info!("Add {:?} on photo #{}!", person, form.image); diesel::insert_into(photo_people) .values((photo_id.eq(form.image), person_id.eq(person.id))) - .execute(&c)?; + .execute(&mut c)?; } Ok(redirect_to_img(form.image)) } @@ -132,7 +132,7 @@ async fn set_grade(context: Context, form: GradeForm) -> Result { use crate::schema::photos::dsl::{grade, photos}; let q = diesel::update(photos.find(form.image)).set(grade.eq(form.grade)); - match q.execute(&context.db()?)? { + match q.execute(&mut context.db()?)? { 1 => { return Ok(redirect_to_img(form.image)); } @@ -173,7 +173,7 @@ async fn set_location(context: Context, form: CoordForm) -> Result { .on_conflict(photo_id) .do_update() .set((latitude.eq(lat), longitude.eq(lng))) - .execute(&context.db()?)?; + .execute(&mut context.db()?)?; match context .overpass() .update_image_places(&context.db_pool(), form.image) diff --git a/src/server/api.rs b/src/server/api.rs index be63ea3..9ff2c18 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -60,9 +60,9 @@ fn w(result: ApiResult) -> Response { } fn login(context: Context, form: LoginForm) -> ApiResult { - let db = context.db()?; + let mut db = context.db()?; let user = form - .validate(&db) + .validate(&mut db) .ok_or_else(|| ApiError::bad_request("login failed"))?; tracing::info!("Api login {user:?} ok"); Ok(LoginOk { @@ -98,7 +98,7 @@ enum ImgIdentifier { } impl ImgIdentifier { - fn load(&self, db: &PgConnection) -> Result, DbError> { + fn load(&self, db: &mut PgConnection) -> Result, DbError> { use crate::schema::photos::dsl as p; match &self { ImgIdentifier::Id(ref id) => { @@ -114,8 +114,8 @@ impl ImgIdentifier { fn get_img(context: Context, q: ImgQuery) -> ApiResult { let id = q.validate().map_err(ApiError::bad_request)?; - let db = context.db()?; - let img = id.load(&db)?.ok_or(NOT_FOUND)?; + let mut db = context.db()?; + let img = id.load(&mut db)?.ok_or(NOT_FOUND)?; if !context.is_authorized() && !img.is_public() { return Err(NOT_FOUND); } @@ -130,12 +130,12 @@ fn make_public(context: Context, q: ImgQuery) -> ApiResult { }); } let id = q.validate().map_err(ApiError::bad_request)?; - let db = context.db()?; - let img = id.load(&db)?.ok_or(NOT_FOUND)?; + let mut db = context.db()?; + let img = id.load(&mut db)?.ok_or(NOT_FOUND)?; use crate::schema::photos::dsl as p; let img = update(p::photos.find(img.id)) .set(p::is_public.eq(true)) - .get_result(&db)?; + .get_result(&mut db)?; Ok(GetImgResult::for_img(&img)) } diff --git a/src/server/autocomplete.rs b/src/server/autocomplete.rs index 5aa2009..c390686 100644 --- a/src/server/autocomplete.rs +++ b/src/server/autocomplete.rs @@ -7,7 +7,7 @@ use crate::schema::photos::dsl as p; use crate::schema::places::dsl as l; use crate::schema::tags::dsl as t; use diesel::prelude::*; -use diesel::sql_types::{Integer, Text}; +use diesel::sql_types::Text; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::fmt::Display; @@ -160,8 +160,8 @@ fn select_tags(context: &Context, term: &AcQ) -> Result> { tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), ))) }; - let db = context.db()?; - Ok(query.order((tpos, t::tag_name)).limit(10).load(&db)?) + let mut db = context.db()?; + Ok(query.order((tpos, t::tag_name)).limit(10).load(&mut db)?) } fn select_people(context: &Context, term: &AcQ) -> Result> { @@ -182,8 +182,11 @@ fn select_people(context: &Context, term: &AcQ) -> Result> { ), ) }; - let db = context.db()?; - Ok(query.order((ppos, h::person_name)).limit(10).load(&db)?) + let mut db = context.db()?; + Ok(query + .order((ppos, h::person_name)) + .limit(10) + .load(&mut db)?) } fn select_places(context: &Context, term: &AcQ) -> Result> { @@ -204,6 +207,6 @@ fn select_places(context: &Context, term: &AcQ) -> Result> { ), ) }; - let db = context.db()?; - Ok(query.order((lpos, l::place_name)).limit(10).load(&db)?) + let mut db = context.db()?; + Ok(query.order((lpos, l::place_name)).limit(10).load(&mut db)?) } diff --git a/src/server/image.rs b/src/server/image.rs index 50865a1..3315b5a 100644 --- a/src/server/image.rs +++ b/src/server/image.rs @@ -10,7 +10,7 @@ use warp::reply::Response; pub async fn show_image(img: ImgName, context: Context) -> Result { use crate::schema::photos::dsl::photos; - let tphoto = photos.find(img.id).first::(&context.db()?); + let tphoto = photos.find(img.id).first::(&mut context.db()?); if let Ok(tphoto) = tphoto { if context.is_authorized() || tphoto.is_public() { if img.size == SizeTag::Large { diff --git a/src/server/login.rs b/src/server/login.rs index 3c13065..9aa66ac 100644 --- a/src/server/login.rs +++ b/src/server/login.rs @@ -1,5 +1,6 @@ use super::{wrap, BuilderExt, Context, ContextFilter, RenderRucte, Result}; use crate::templates; +use diesel::pg::PgConnection; use diesel::prelude::*; use lazy_regex::regex_is_match; use serde::Deserialize; @@ -36,7 +37,8 @@ struct NextQ { fn post_login(context: Context, form: LoginForm) -> Result { let next = sanitize_next(form.next.as_ref().map(AsRef::as_ref)); - if let Some(user) = form.validate(&*context.db()?) { + let mut db = context.db()?; + if let Some(user) = form.validate(&mut db) { let token = context.make_token(&user)?; return Ok(Builder::new() .header( @@ -61,7 +63,7 @@ pub struct LoginForm { impl LoginForm { /// Retur user if and only if password is correct for user. - pub fn validate(&self, db: &PgConnection) -> Option { + pub fn validate(&self, db: &mut PgConnection) -> Option { use crate::schema::users::dsl::*; if let Ok(hash) = users .filter(username.eq(&self.user)) diff --git a/src/server/mod.rs b/src/server/mod.rs index 317b068..359da8e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -141,21 +141,21 @@ async fn static_file(name: Tail) -> Result { fn random_image(context: Context) -> Result { use crate::schema::photos::dsl::id; - use diesel::expression::dsl::sql; + use diesel::dsl::sql; use diesel::sql_types::Integer; let photo = Photo::query(context.is_authorized()) .select(id) .limit(1) .order(sql::("random()")) - .first(&context.db()?)?; + .first(&mut context.db()?)?; info!("Random: {:?}", photo); Ok(redirect_to_img(photo)) } fn photo_details(id: i32, context: Context) -> Result { - let c = context.db()?; - let photo = or_404q!(PhotoDetails::load(id, &c), context); + let mut c = context.db()?; + let photo = or_404q!(PhotoDetails::load(id, &mut c), context); if context.is_authorized() || photo.is_public() { Ok(Builder::new().html(|o| { diff --git a/src/server/search.rs b/src/server/search.rs index 1ec0fb0..ef6a867 100644 --- a/src/server/search.rs +++ b/src/server/search.rs @@ -19,8 +19,8 @@ pub fn search( context: Context, query: Vec<(String, String)>, ) -> Result { - let db = context.db()?; - let query = SearchQuery::load(query, &db)?; + let mut db = context.db()?; + let query = SearchQuery::load(query, &mut db)?; let mut photos = Photo::query(context.is_authorized()); if let Some(since) = query.since.as_ref() { @@ -71,10 +71,10 @@ pub fn search( let photos = photos .order((p::date.desc().nulls_last(), p::id.desc())) - .load(&db)?; + .load(&mut db)?; let n = photos.len(); - let coords = get_positions(&photos, &db)?; + let coords = get_positions(&photos, &mut db)?; let links = split_to_group_links(&photos, &query.to_base_url(), true); Ok(Builder::new().html(|o| { @@ -104,7 +104,7 @@ pub struct Filter { } impl Filter { - fn load(val: &str, db: &PgConnection) -> Option> { + fn load(val: &str, db: &mut PgConnection) -> Option> { let (inc, slug) = match val.strip_prefix('!') { Some(val) => (false, val), None => (true, val), @@ -120,7 +120,10 @@ impl Filter { } impl SearchQuery { - fn load(query: Vec<(String, String)>, db: &PgConnection) -> Result { + fn load( + query: Vec<(String, String)>, + db: &mut PgConnection, + ) -> Result { let mut result = SearchQuery::default(); let (mut s_d, mut s_t, mut u_d, mut u_t) = (None, None, None, None); for (key, val) in &query { @@ -218,7 +221,7 @@ impl QueryDateTime { let until_midnight = NaiveTime::from_hms_milli(23, 59, 59, 999); QueryDateTime::new(datetime_from_parts(date, time, until_midnight)) } - fn from_img(photo_id: i32, db: &PgConnection) -> Result { + fn from_img(photo_id: i32, db: &mut PgConnection) -> Result { Ok(QueryDateTime::new( p::photos .select(p::date) diff --git a/src/server/splitlist.rs b/src/server/splitlist.rs index 1b3832e..0123fab 100644 --- a/src/server/splitlist.rs +++ b/src/server/splitlist.rs @@ -13,26 +13,27 @@ pub fn links_by_time( range: ImgRange, with_date: bool, ) -> Result<(Vec, Vec<(Coord, i32)>)> { - let c = context.db()?; + let mut c = context.db()?; use crate::schema::photos::dsl::{date, id}; let photos = - if let Some(from_date) = range.from.map(|i| date_of_img(&c, i)) { + if let Some(from_date) = range.from.map(|i| date_of_img(&mut c, i)) { photos.filter(date.ge(from_date)) } else { photos }; - let photos = if let Some(to_date) = range.to.map(|i| date_of_img(&c, i)) { - photos.filter(date.le(to_date)) - } else { - photos - }; + let photos = + if let Some(to_date) = range.to.map(|i| date_of_img(&mut c, i)) { + photos.filter(date.le(to_date)) + } else { + photos + }; let photos = photos .order((date.desc().nulls_last(), id.desc())) - .load(&c)?; + .load(&mut c)?; let baseurl = UrlString::new(context.path_without_query()); Ok(( split_to_group_links(&photos, &baseurl, with_date), - get_positions(&photos, &c)?, + get_positions(&photos, &mut c)?, )) } @@ -58,7 +59,7 @@ pub fn split_to_group_links( pub fn get_positions( photos: &[Photo], - c: &PgConnection, + c: &mut PgConnection, ) -> Result> { use crate::schema::positions::dsl::*; Ok(positions diff --git a/src/server/views_by_category.rs b/src/server/views_by_category.rs index c95b658..bb5720f 100644 --- a/src/server/views_by_category.rs +++ b/src/server/views_by_category.rs @@ -57,7 +57,7 @@ fn person_all(context: Context) -> Result { pp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), ))) }; - let images = query.order(person_name).load(&context.db()?)?; + let images = query.order(person_name).load(&mut context.db()?)?; Ok(Builder::new().html(|o| templates::people(o, &context, &images))?) } @@ -67,9 +67,11 @@ fn person_one( context: Context, ) -> Result { use crate::schema::people::dsl::{people, slug}; - let c = context.db()?; - let person = - or_404q!(people.filter(slug.eq(tslug)).first::(&c), context); + let mut c = context.db()?; + let person = or_404q!( + people.filter(slug.eq(tslug)).first::(&mut c), + context + ); use crate::schema::photo_people::dsl::{ person_id, photo_id, photo_people, }; @@ -98,7 +100,7 @@ fn tag_all(context: Context) -> Result { tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), ))) }; - let taggs = query.load(&context.db()?)?; + let taggs = query.load(&mut context.db()?)?; Ok(Builder::new().html(|o| templates::tags(o, &context, &taggs))?) } @@ -109,7 +111,7 @@ fn tag_one( ) -> Result { use crate::schema::tags::dsl::{slug, tags}; let tag = or_404q!( - tags.filter(slug.eq(tslug)).first::(&context.db()?), + tags.filter(slug.eq(tslug)).first::(&mut context.db()?), context ); @@ -135,7 +137,7 @@ fn place_all(context: Context) -> Result { pp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), ))) }; - let found = query.order(place_name).load(&context.db()?)?; + let found = query.order(place_name).load(&mut context.db()?)?; Ok(Builder::new().html(|o| templates::places(o, &context, &found))?) } @@ -146,7 +148,9 @@ fn place_one( ) -> Result { use crate::schema::places::dsl::{places, slug}; let place = or_404q!( - places.filter(slug.eq(tslug)).first::(&context.db()?), + places + .filter(slug.eq(tslug)) + .first::(&mut context.db()?), context ); diff --git a/src/server/views_by_date.rs b/src/server/views_by_date.rs index 2fd2edf..a59ebaf 100644 --- a/src/server/views_by_date.rs +++ b/src/server/views_by_date.rs @@ -7,9 +7,9 @@ use crate::models::{Photo, SizeTag}; use crate::templates::{self, RenderRucte}; use chrono::naive::{NaiveDate, NaiveDateTime}; use chrono::{Datelike, Duration, Local}; -use diesel::dsl::sql; +use diesel::dsl::count_star; use diesel::prelude::*; -use diesel::sql_types::{BigInt, Integer, Nullable}; +use diesel::sql_types::{Bool, Nullable, Timestamp}; use serde::Deserialize; use warp::filters::BoxedFilter; use warp::http::response::Builder; @@ -74,18 +74,47 @@ pub fn routes(s: ContextFilter) -> BoxedFilter<(Response,)> { .boxed() } +sql_function! { + #[aggregate] + fn year_of_timestamp(date: Nullable) -> Nullable +} +sql_function! { + #[aggregate] + fn month_of_timestamp(date: Nullable) -> Nullable +} +sql_function! { + #[aggregate] + fn day_of_timestamp(date: Nullable) -> Nullable +} + +mod filter { + use diesel::sql_types::{Nullable, Timestamp}; + + sql_function! { + fn year_of_timestamp(date: Nullable) -> Nullable + } + sql_function! { + fn month_of_timestamp(date: Nullable) -> Nullable + } + sql_function! { + fn day_of_timestamp(date: Nullable) -> Nullable + } +} fn all_years(context: Context) -> Result { use crate::schema::photos::dsl as p; - let db = context.db()?; - let groups = Photo::query(context.is_authorized()) - .select(sql::<(Nullable, BigInt)>( - "cast(extract(year from date) as int) y, count(*)", - )) - .group_by(sql::>("y")) - .order(sql::>("y").desc().nulls_last()) - .load::<(Option, i64)>(&db)? + let mut db = context.db()?; + let y = year_of_timestamp(p::date); + let groups = p::photos + .filter(p::path.not_like("%.CR2")) + .filter(p::path.not_like("%.dng")) + .filter(p::is_public.or::<_, Bool>(context.is_authorized())) + .select((y, count_star())) + .group_by(y) + .order(y.desc().nulls_last()) + .load::<(Option, i64)>(&mut db)? .iter() .map(|&(year, count)| { + let year: Option = year.map(|y| y as i32); let q = Photo::query(context.is_authorized()) .order((p::grade.desc().nulls_last(), p::date.asc())) .limit(1); @@ -95,7 +124,7 @@ fn all_years(context: Context) -> Result { } else { q.filter(p::date.is_null()) }; - let photo = photo.first::(&db)?; + let photo = photo.first::(&mut db)?; Ok(PhotoLink { title: Some( year.map(|y| format!("{}", y)) @@ -122,25 +151,27 @@ fn months_in_year(year: i32, context: Context) -> Result { use crate::schema::photos::dsl as p; let title: String = format!("Photos from {}", year); - let db = context.db()?; - let groups = Photo::query(context.is_authorized()) + let mut db = context.db()?; + let m = month_of_timestamp(p::date); + let groups = p::photos + .filter(p::path.not_like("%.CR2")) + .filter(p::path.not_like("%.dng")) + .filter(p::is_public.or::<_, Bool>(context.is_authorized())) .filter(p::date.ge(start_of_year(year))) .filter(p::date.lt(start_of_year(year + 1))) - .select(sql::<(Integer, BigInt)>( - "cast(extract(month from date) as int) m, count(*)", - )) - .group_by(sql::("m")) - .order(sql::("m").desc().nulls_last()) - .load::<(i32, i64)>(&db)? + .select((m, count_star())) + .group_by(m) + .order(m.desc().nulls_last()) + .load::<(Option, i64)>(&mut db)? .iter() .map(|&(month, count)| { - let month = month as u32; + let month = month.unwrap() as u32; // cant be null when in range! let photo = Photo::query(context.is_authorized()) .filter(p::date.ge(start_of_month(year, month))) .filter(p::date.lt(start_of_month(year, month + 1))) .order((p::grade.desc().nulls_last(), p::date.asc())) .limit(1) - .first::(&db)?; + .first::(&mut db)?; Ok(PhotoLink { title: Some(monthname(month).to_string()), @@ -163,7 +194,7 @@ fn months_in_year(year: i32, context: Context) -> Result { .filter(p::date.ge(start_of_year(year))) .filter(p::date.lt(start_of_year(year + 1))) .select((photo_id, latitude, longitude)) - .load(&db)? + .load(&mut db)? .into_iter() .map(|(p_id, lat, long): (i32, i32, i32)| { ((lat, long).into(), p_id) @@ -186,22 +217,24 @@ fn start_of_month(year: i32, month: u32) -> NaiveDateTime { fn days_in_month(year: i32, month: u32, context: Context) -> Result { use crate::schema::photos::dsl as p; + let d = day_of_timestamp(p::date); let lpath: Vec = vec![Link::year(year)]; let title: String = format!("Photos from {} {}", monthname(month), year); - let db = context.db()?; - let groups = Photo::query(context.is_authorized()) + let mut db = context.db()?; + let groups = p::photos + .filter(p::path.not_like("%.CR2")) + .filter(p::path.not_like("%.dng")) + .filter(p::is_public.or::<_, Bool>(context.is_authorized())) .filter(p::date.ge(start_of_month(year, month))) .filter(p::date.lt(start_of_month(year, month + 1))) - .select(sql::<(Integer, BigInt)>( - "cast(extract(day from date) as int) d, count(*)", - )) - .group_by(sql::("d")) - .order(sql::("d").desc().nulls_last()) - .load::<(i32, i64)>(&db)? + .select((d, count_star())) + .group_by(d) + .order(d.desc().nulls_last()) + .load::<(Option, i64)>(&mut db)? .iter() .map(|&(day, count)| { - let day = day as u32; + let day = day.unwrap() as u32; let fromdate = NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0); let photo = Photo::query(context.is_authorized()) @@ -209,7 +242,7 @@ fn days_in_month(year: i32, month: u32, context: Context) -> Result { .filter(p::date.lt(fromdate + Duration::days(1))) .order((p::grade.desc().nulls_last(), p::date.asc())) .limit(1) - .first::(&db)?; + .first::(&mut db)?; Ok(PhotoLink { title: Some(format!("{}", day)), @@ -230,7 +263,7 @@ fn days_in_month(year: i32, month: u32, context: Context) -> Result { .filter(p::date.ge(start_of_month(year, month))) .filter(p::date.lt(start_of_month(year, month + 1))) .select((ps::photo_id, ps::latitude, ps::longitude)) - .load(&db)? + .load(&mut db)? .into_iter() .map(|(p_id, lat, long): (i32, i32, i32)| { ((lat, long).into(), p_id) @@ -248,7 +281,7 @@ fn all_null_date(context: Context) -> Result { .filter(p::date.is_null()) .order(p::path.asc()) .limit(500) - .load(&context.db()?)? + .load(&mut context.db()?)? .iter() .map(PhotoLink::no_title) .collect::>(); @@ -303,40 +336,39 @@ fn on_this_day(context: Context) -> Result { let today = Local::now(); (today.month(), today.day()) }; - let db = context.db()?; + let mut db = context.db()?; let pos = Photo::query(context.is_authorized()) .inner_join(ps::positions) - .filter( - sql("extract(month from date)=").bind::(month as i32), - ) - .filter(sql("extract(day from date)=").bind::(day as i32)) + .filter(filter::month_of_timestamp(p::date).eq(month as i16)) + .filter(filter::day_of_timestamp(p::date).eq(day as i16)) .select((ps::photo_id, ps::latitude, ps::longitude)) - .load(&db)? + .load(&mut db)? .into_iter() .map(|(p_id, lat, long): (i32, i32, i32)| ((lat, long).into(), p_id)) .collect::>(); - let photos = Photo::query(context.is_authorized()) - .select(sql::<(Integer, BigInt)>( - "cast(extract(year from date) as int) y, count(*)", - )) - .group_by(sql::("y")) - .filter( - sql("extract(month from date)=").bind::(month as i32), - ) - .filter(sql("extract(day from date)=").bind::(day as i32)) - .order(sql::("y").desc()) - .load::<(i32, i64)>(&db)? + let y = year_of_timestamp(p::date); + let photos = p::photos + .filter(p::path.not_like("%.CR2")) + .filter(p::path.not_like("%.dng")) + .filter(p::is_public.or::<_, Bool>(context.is_authorized())) + .filter(filter::month_of_timestamp(p::date).eq(month as i16)) + .filter(filter::day_of_timestamp(p::date).eq(day as i16)) + .select((y, count_star())) + .group_by(y) + .order(y.desc()) + .load::<(Option, i64)>(&mut db)? .iter() .map(|&(year, count)| { + let year = year.unwrap(); // matching date can't be null let fromdate = - NaiveDate::from_ymd(year, month as u32, day).and_hms(0, 0, 0); + NaiveDate::from_ymd(year.into(), month, day).and_hms(0, 0, 0); let photo = Photo::query(context.is_authorized()) .filter(p::date.ge(fromdate)) .filter(p::date.lt(fromdate + Duration::days(1))) .order((p::grade.desc().nulls_last(), p::date.asc())) .limit(1) - .first::(&db)?; + .first::(&mut db)?; Ok(PhotoLink { title: Some(format!("{}", year)), href: format!("/{}/{}/{}", year, month, day), @@ -360,8 +392,8 @@ fn on_this_day(context: Context) -> Result { fn next_image(context: Context, param: FromParam) -> Result { use crate::schema::photos::dsl as p; - let db = context.db()?; - let from_date = or_404!(date_of_img(&db, param.from), context); + let mut db = context.db()?; + let from_date = or_404!(date_of_img(&mut db, param.from), context); let photo = or_404q!( Photo::query(context.is_authorized()) .select(p::id) @@ -371,7 +403,7 @@ fn next_image(context: Context, param: FromParam) -> Result { .or(p::date.eq(from_date).and(p::id.gt(param.from))), ) .order((p::date, p::id)) - .first::(&db), + .first::(&mut db), context ); Ok(redirect_to_img(photo)) @@ -379,8 +411,8 @@ fn next_image(context: Context, param: FromParam) -> Result { fn prev_image(context: Context, param: FromParam) -> Result { use crate::schema::photos::dsl as p; - let db = context.db()?; - let from_date = or_404!(date_of_img(&db, param.from), context); + let mut db = context.db()?; + let from_date = or_404!(date_of_img(&mut db, param.from), context); let photo = or_404q!( Photo::query(context.is_authorized()) .select(p::id) @@ -390,7 +422,7 @@ fn prev_image(context: Context, param: FromParam) -> Result { .or(p::date.eq(from_date).and(p::id.lt(param.from))), ) .order((p::date.desc().nulls_last(), p::id.desc())) - .first::(&db), + .first::(&mut db), context ); Ok(redirect_to_img(photo)) @@ -401,7 +433,10 @@ struct FromParam { from: i32, } -pub fn date_of_img(db: &PgConnection, photo_id: i32) -> Option { +pub fn date_of_img( + db: &mut PgConnection, + photo_id: i32, +) -> Option { use crate::schema::photos::dsl as p; p::photos .find(photo_id)