Use diesel 2.0

Most of the changes are for database access now requiring an &mut
connection.
This commit is contained in:
Rasmus Kaj 2022-04-30 16:52:15 +02:00
parent f7c3d60e77
commit 03dd6a5465
23 changed files with 252 additions and 173 deletions

View File

@ -11,6 +11,9 @@ The format is based on
* Improved logging by using tracing and tracing-subscriber rather than * Improved logging by using tracing and tracing-subscriber rather than
log and env_logger. log and env_logger.
* Four more kinds of OSM areas to recognize. * 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. * Update ructe to 0.15.0.
* Add this changelog. * Add this changelog.

View File

@ -11,7 +11,7 @@ ructe = { version = "0.15.0", features = ["sass", "warp03"] }
[dependencies] [dependencies]
brotli = "3.3.0" 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"] } clap = { version = "3.2.8", features = ["derive", "terminal_size", "wrap_help", "env"] }
dotenv = "0.15" dotenv = "0.15"
flate2 = "1.0.14" flate2 = "1.0.14"
@ -39,7 +39,7 @@ version = "1.1.1"
[dependencies.diesel] [dependencies.diesel]
default-features = false default-features = false
features = ["r2d2", "chrono", "postgres"] features = ["r2d2", "chrono", "postgres"]
version = "1.4.0" version = "2.0.0"
[dependencies.warp] [dependencies.warp]
default-features = false default-features = false

View File

@ -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;

View File

@ -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$;

View File

@ -23,15 +23,15 @@ pub struct Findphotos {
impl Findphotos { impl Findphotos {
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let pd = PhotosDir::new(&self.photos.photos_dir); let pd = PhotosDir::new(&self.photos.photos_dir);
let db = self.db.connect()?; let mut db = self.db.connect()?;
if !self.base.is_empty() { if !self.base.is_empty() {
for base in &self.base { 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)) Error::Other(format!("Failed to crawl {}: {}", base, e))
})?; })?;
} }
} else { } 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)) Error::Other(format!("Failed to crawl: {}", e))
})?; })?;
} }
@ -40,35 +40,36 @@ impl Findphotos {
} }
fn crawl( fn crawl(
db: &PgConnection, db: &mut PgConnection,
photos: &PhotosDir, photos: &PhotosDir,
only_in: &Path, only_in: &Path,
) -> Result<(), Error> { ) -> Result<(), Error> {
photos.find_files( photos.find_files(only_in, &mut |path, exif| match save_photo(
only_in, db, path, exif,
&|path, exif| match save_photo(db, path, exif) { ) {
Ok(()) => debug!("Saved photo {}", path), Ok(()) => debug!("Saved photo {}", path),
Err(e) => warn!("Failed to save photo {}: {:?}", path, e), Err(e) => warn!("Failed to save photo {}: {:?}", path, e),
}, })?;
)?;
Ok(()) Ok(())
} }
fn save_photo( fn save_photo(
db: &PgConnection, db: &mut PgConnection,
file_path: &str, file_path: &str,
exif: &ExifData, exif: &ExifData,
) -> Result<(), Error> { ) -> Result<(), Error> {
let width = exif.width.ok_or(Error::MissingWidth)?; let width = exif.width.ok_or(Error::MissingWidth)?;
let height = exif.height.ok_or(Error::MissingHeight)?; 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( let photo = match Photo::create_or_set_basics(
db, db,
file_path, file_path,
width as i32, width as i32,
height as i32, height as i32,
exif.date(), exif.date(),
exif.rotation()?, rot,
find_camera(db, exif)?, cam,
)? { )? {
Modification::Created(photo) => { Modification::Created(photo) => {
info!("Created #{}, {}", photo.id, photo.path); info!("Created #{}, {}", photo.id, photo.path);
@ -116,7 +117,7 @@ fn save_photo(
} }
fn find_camera( fn find_camera(
db: &PgConnection, db: &mut PgConnection,
exif: &ExifData, exif: &ExifData,
) -> Result<Option<Camera>, Error> { ) -> Result<Option<Camera>, Error> {
if let Some((make, model)) = exif.camera() { if let Some((make, model)) = exif.camera() {

View File

@ -30,7 +30,7 @@ pub struct Makepublic {
impl Makepublic { impl Makepublic {
pub fn run(&self) -> Result<(), Error> { pub fn run(&self) -> Result<(), Error> {
let db = self.db.connect()?; let mut db = self.db.connect()?;
match ( match (
self.list.as_ref().map(AsRef::as_ref), self.list.as_ref().map(AsRef::as_ref),
&self.tag, &self.tag,
@ -38,12 +38,12 @@ impl Makepublic {
) { ) {
(Some("-"), None, None) => { (Some("-"), None, None) => {
let list = io::stdin(); let list = io::stdin();
by_file_list(&db, list.lock())?; by_file_list(&mut db, list.lock())?;
Ok(()) Ok(())
} }
(Some(list), None, None) => { (Some(list), None, None) => {
let list = BufReader::new(File::open(list)?); let list = BufReader::new(File::open(list)?);
by_file_list(&db, list) by_file_list(&mut db, list)
} }
(None, Some(tag), None) => { (None, Some(tag), None) => {
use crate::schema::photo_tags::dsl as pt; use crate::schema::photo_tags::dsl as pt;
@ -60,17 +60,17 @@ impl Makepublic {
), ),
) )
.set(p::is_public.eq(true)) .set(p::is_public.eq(true))
.execute(&db)?; .execute(&mut db)?;
println!("Made {} images public.", n); println!("Made {} images public.", n);
Ok(()) Ok(())
} }
(None, None, Some(image)) => one(&db, image), (None, None, Some(image)) => one(&mut db, image),
_ => Err(Error::Other("bad command".to_string())), _ => 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::*; use crate::schema::photos::dsl::*;
match update(photos.filter(path.eq(&tpath))) match update(photos.filter(path.eq(&tpath)))
.set(is_public.eq(true)) .set(is_public.eq(true))
@ -88,7 +88,7 @@ pub fn one(db: &PgConnection, tpath: &str) -> Result<(), Error> {
} }
pub fn by_file_list<In: BufRead + Sized>( pub fn by_file_list<In: BufRead + Sized>(
db: &PgConnection, db: &mut PgConnection,
list: In, list: In,
) -> Result<(), Error> { ) -> Result<(), Error> {
for line in list.lines() { for line in list.lines() {

View File

@ -37,7 +37,7 @@ impl Args {
let (mut n, mut n_stored) = (0, 0); let (mut n, mut n_stored) = (0, 0);
let photos = Photo::query(true) let photos = Photo::query(true)
.order((is_public.desc(), date.desc().nulls_last())) .order((is_public.desc(), date.desc().nulls_last()))
.load::<Photo>(&self.db.connect()?)?; .load::<Photo>(&mut self.db.connect()?)?;
let no_expire = 0; let no_expire = 0;
let pd = PhotosDir::new(&self.photos.photos_dir); let pd = PhotosDir::new(&self.photos.photos_dir);
for photo in photos { for photo in photos {

View File

@ -3,12 +3,12 @@ use crate::schema::people::dsl::people;
use crate::schema::photos::dsl::photos; use crate::schema::photos::dsl::photos;
use crate::schema::places::dsl::places; use crate::schema::places::dsl::places;
use crate::schema::tags::dsl::tags; 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::pg::PgConnection;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sql_types::{BigInt, Double, Nullable}; 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!( println!(
"There are {} photos in total.", "There are {} photos in total.",
photos.select(count_star()).first::<i64>(db)?, photos.select(count_star()).first::<i64>(db)?,

View File

@ -6,7 +6,7 @@ use djangohashers::make_password;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use std::iter::Iterator; use std::iter::Iterator;
pub fn list(db: &PgConnection) -> Result<(), Error> { pub fn list(db: &mut PgConnection) -> Result<(), Error> {
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;
println!( println!(
"Existing users: {:?}.", "Existing users: {:?}.",
@ -15,7 +15,7 @@ pub fn list(db: &PgConnection) -> Result<(), Error> {
Ok(()) 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 pword = random_password(14);
let hashword = make_password(&pword); let hashword = make_password(&pword);
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;

View File

@ -38,7 +38,7 @@ impl Fetchplaces {
)) ))
.order(pos::photo_id.desc()) .order(pos::photo_id.desc())
.limit(self.limit) .limit(self.limit)
.load::<(i32, Coord)>(&db.get()?)?; .load::<(i32, Coord)>(&mut db.get()?)?;
for (photo_id, coord) in result { for (photo_id, coord) in result {
println!("Find places for #{}, {:?}", photo_id, coord); println!("Find places for #{}, {:?}", photo_id, coord);
self.overpass.update_image_places(&db, photo_id).await?; self.overpass.update_image_places(&db, photo_id).await?;
@ -74,7 +74,9 @@ impl OverpassOpt {
.filter(photo_id.eq(image)) .filter(photo_id.eq(image))
.select((latitude, longitude)) .select((latitude, longitude))
.first::<Coord>( .first::<Coord>(
&db.get().map_err(|e| Error::Pool(image, e.to_string()))?, &mut db
.get()
.map_err(|e| Error::Pool(image, e.to_string()))?,
) )
.optional() .optional()
.map_err(|e| Error::Db(image, e))? .map_err(|e| Error::Db(image, e))?
@ -96,14 +98,16 @@ impl OverpassOpt {
.and_then(|o| o.get("elements")) .and_then(|o| o.get("elements"))
.and_then(Value::as_array) .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 { for obj in elements {
if let (Some(t_osm_id), Some((name, level))) = if let (Some(t_osm_id), Some((name, level))) =
(osm_id(obj), name_and_level(obj)) (osm_id(obj), name_and_level(obj))
{ {
debug!("{}: {} (level {})", t_osm_id, name, level); debug!("{}: {} (level {})", t_osm_id, name, level);
let place = get_or_create_place(&c, t_osm_id, name, level) let place =
.map_err(|e| Error::Db(image, e))?; get_or_create_place(&mut c, t_osm_id, name, level)
.map_err(|e| Error::Db(image, e))?;
if place.osm_id.is_none() { if place.osm_id.is_none() {
debug!("Matched {:?} by name, update osm info", place); debug!("Matched {:?} by name, update osm info", place);
use crate::schema::places::dsl::*; use crate::schema::places::dsl::*;
@ -113,7 +117,7 @@ impl OverpassOpt {
osm_id.eq(Some(t_osm_id)), osm_id.eq(Some(t_osm_id)),
osm_level.eq(level), osm_level.eq(level),
)) ))
.execute(&c) .execute(&mut c)
.map_err(|e| Error::Db(image, e))?; .map_err(|e| Error::Db(image, e))?;
} }
use crate::models::PhotoPlace; use crate::models::PhotoPlace;
@ -121,7 +125,7 @@ impl OverpassOpt {
let q = photo_places let q = photo_places
.filter(photo_id.eq(image)) .filter(photo_id.eq(image))
.filter(place_id.eq(place.id)); .filter(place_id.eq(place.id));
if q.first::<PhotoPlace>(&c).is_ok() { if q.first::<PhotoPlace>(&mut c).is_ok() {
debug!( debug!(
"Photo #{} already has {} ({})", "Photo #{} already has {} ({})",
image, place.id, place.place_name image, place.id, place.place_name
@ -132,7 +136,7 @@ impl OverpassOpt {
photo_id.eq(image), photo_id.eq(image),
place_id.eq(place.id), place_id.eq(place.id),
)) ))
.execute(&c) .execute(&mut c)
.map_err(|e| Error::Db(image, e))?; .map_err(|e| Error::Db(image, e))?;
} }
} else { } else {
@ -291,7 +295,7 @@ fn tag_str<'a>(tags: &'a Value, name: &str) -> Option<&'a str> {
} }
fn get_or_create_place( fn get_or_create_place(
c: &PgConnection, c: &mut PgConnection,
t_osm_id: i64, t_osm_id: i64,
name: &str, name: &str,
level: i16, level: i16,

View File

@ -102,9 +102,11 @@ async fn run(args: &RPhotos) -> Result<(), Error> {
match args { match args {
RPhotos::Findphotos(cmd) => cmd.run(), RPhotos::Findphotos(cmd) => cmd.run(),
RPhotos::Makepublic(cmd) => cmd.run(), RPhotos::Makepublic(cmd) => cmd.run(),
RPhotos::Stats(db) => show_stats(&db.connect()?), RPhotos::Stats(db) => show_stats(&mut db.connect()?),
RPhotos::Userlist { db } => users::list(&db.connect()?), RPhotos::Userlist { db } => users::list(&mut db.connect()?),
RPhotos::Userpass { db, user } => users::passwd(&db.connect()?, user), RPhotos::Userpass { db, user } => {
users::passwd(&mut db.connect()?, user)
}
RPhotos::Fetchplaces(cmd) => cmd.run().await, RPhotos::Fetchplaces(cmd) => cmd.run().await,
RPhotos::Precache(cmd) => cmd.run().await, RPhotos::Precache(cmd) => cmd.run().await,
RPhotos::Storestatics { dir } => storestatics::to_dir(dir), RPhotos::Storestatics { dir } => storestatics::to_dir(dir),

View File

@ -28,7 +28,7 @@ pub struct PhotoDetails {
pub camera: Option<Camera>, pub camera: Option<Camera>,
} }
impl PhotoDetails { impl PhotoDetails {
pub fn load(id: i32, db: &PgConnection) -> Result<Self, Error> { pub fn load(id: i32, db: &mut PgConnection) -> Result<Self, Error> {
use crate::schema::photos::dsl::photos; use crate::schema::photos::dsl::photos;
let photo = photos.find(id).first::<Photo>(db)?; let photo = photos.find(id).first::<Photo>(db)?;
let attribution = photo let attribution = photo
@ -133,7 +133,7 @@ impl Photo {
} }
pub fn update_by_path( pub fn update_by_path(
db: &PgConnection, db: &mut PgConnection,
file_path: &str, file_path: &str,
newwidth: i32, newwidth: i32,
newheight: i32, newheight: i32,
@ -178,7 +178,7 @@ impl Photo {
} }
pub fn create_or_set_basics( pub fn create_or_set_basics(
db: &PgConnection, db: &mut PgConnection,
file_path: &str, file_path: &str,
newwidth: i32, newwidth: i32,
newheight: i32, newheight: i32,
@ -239,7 +239,7 @@ impl Photo {
} }
pub trait Facet { pub trait Facet {
fn by_slug(slug: &str, db: &PgConnection) -> Result<Self, Error> fn by_slug(slug: &str, db: &mut PgConnection) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
} }
@ -252,7 +252,7 @@ pub struct Tag {
} }
impl Facet for Tag { impl Facet for Tag {
fn by_slug(slug: &str, db: &PgConnection) -> Result<Tag, Error> { fn by_slug(slug: &str, db: &mut PgConnection) -> Result<Tag, Error> {
t::tags.filter(t::slug.eq(slug)).first(db) t::tags.filter(t::slug.eq(slug)).first(db)
} }
} }
@ -273,7 +273,7 @@ pub struct Person {
impl Person { impl Person {
pub fn get_or_create_name( pub fn get_or_create_name(
db: &PgConnection, db: &mut PgConnection,
name: &str, name: &str,
) -> Result<Person, Error> { ) -> Result<Person, Error> {
h::people h::people
@ -291,7 +291,7 @@ impl Person {
} }
impl Facet for Person { impl Facet for Person {
fn by_slug(slug: &str, db: &PgConnection) -> Result<Person, Error> { fn by_slug(slug: &str, db: &mut PgConnection) -> Result<Person, Error> {
h::people.filter(h::slug.eq(slug)).first(db) h::people.filter(h::slug.eq(slug)).first(db)
} }
} }
@ -313,7 +313,7 @@ pub struct Place {
} }
impl Facet for Place { impl Facet for Place {
fn by_slug(slug: &str, db: &PgConnection) -> Result<Place, Error> { fn by_slug(slug: &str, db: &mut PgConnection) -> Result<Place, Error> {
l::places.filter(l::slug.eq(slug)).first(db) l::places.filter(l::slug.eq(slug)).first(db)
} }
} }
@ -334,7 +334,7 @@ pub struct Camera {
impl Camera { impl Camera {
pub fn get_or_create( pub fn get_or_create(
db: &PgConnection, db: &mut PgConnection,
make: &str, make: &str,
modl: &str, modl: &str,
) -> Result<Camera, Error> { ) -> Result<Camera, Error> {
@ -362,8 +362,8 @@ pub struct Coord {
impl Queryable<(Integer, Integer), Pg> for Coord { impl Queryable<(Integer, Integer), Pg> for Coord {
type Row = (i32, i32); type Row = (i32, i32);
fn build(row: Self::Row) -> Self { fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
Coord::from((row.0, row.1)) Ok(Coord::from((row.0, row.1)))
} }
} }

View File

@ -31,7 +31,7 @@ impl PhotosDir {
pub fn find_files( pub fn find_files(
&self, &self,
dir: &Path, dir: &Path,
cb: &dyn Fn(&str, &ExifData), cb: &mut dyn FnMut(&str, &ExifData),
) -> io::Result<()> { ) -> io::Result<()> {
let absdir = self.basedir.join(dir); let absdir = self.basedir.join(dir);
if fs::metadata(&absdir)?.is_dir() { if fs::metadata(&absdir)?.is_dir() {

View File

@ -35,8 +35,8 @@ fn rotate(context: Context, form: RotateForm) -> Result<Response> {
} }
info!("Should rotate #{} by {}", form.image, form.angle); info!("Should rotate #{} by {}", form.image, form.angle);
use crate::schema::photos::dsl::photos; use crate::schema::photos::dsl::photos;
let c = context.db()?; let mut c = context.db()?;
let c: &PgConnection = &c; let c: &mut PgConnection = &mut c;
let mut image = let mut image =
or_404q!(photos.find(form.image).first::<Photo>(c), context); or_404q!(photos.find(form.image).first::<Photo>(c), context);
let newvalue = (360 + image.rotation + form.angle) % 360; let newvalue = (360 + image.rotation + form.angle) % 360;
@ -58,19 +58,19 @@ async fn set_tag(context: Context, form: TagForm) -> Result<Response> {
if !context.is_authorized() { if !context.is_authorized() {
return Err(ViewError::PermissionDenied); return Err(ViewError::PermissionDenied);
} }
let c = context.db()?; let mut c = context.db()?;
use crate::models::Tag; use crate::models::Tag;
let tag = { let tag = {
use crate::schema::tags::dsl::*; use crate::schema::tags::dsl::*;
tags.filter(tag_name.ilike(&form.tag)) tags.filter(tag_name.ilike(&form.tag))
.first::<Tag>(&c) .first::<Tag>(&mut c)
.or_else(|_| { .or_else(|_| {
diesel::insert_into(tags) diesel::insert_into(tags)
.values(( .values((
tag_name.eq(&form.tag), tag_name.eq(&form.tag),
slug.eq(&slugify(&form.tag)), slug.eq(&slugify(&form.tag)),
)) ))
.get_result::<Tag>(&c) .get_result::<Tag>(&mut c)
})? })?
}; };
use crate::schema::photo_tags::dsl::*; use crate::schema::photo_tags::dsl::*;
@ -78,13 +78,13 @@ async fn set_tag(context: Context, form: TagForm) -> Result<Response> {
.filter(photo_id.eq(form.image)) .filter(photo_id.eq(form.image))
.filter(tag_id.eq(tag.id)) .filter(tag_id.eq(tag.id))
.count(); .count();
if q.get_result::<i64>(&c)? > 0 { if q.get_result::<i64>(&mut c)? > 0 {
info!("Photo #{} already has {:?}", form.image, form.tag); info!("Photo #{} already has {:?}", form.image, form.tag);
} else { } else {
info!("Add {:?} on photo #{}!", form.tag, form.image); info!("Add {:?} on photo #{}!", form.tag, form.image);
diesel::insert_into(photo_tags) diesel::insert_into(photo_tags)
.values((photo_id.eq(form.image), tag_id.eq(tag.id))) .values((photo_id.eq(form.image), tag_id.eq(tag.id)))
.execute(&c)?; .execute(&mut c)?;
} }
Ok(redirect_to_img(form.image)) Ok(redirect_to_img(form.image))
} }
@ -99,20 +99,20 @@ async fn set_person(context: Context, form: PersonForm) -> Result<Response> {
if !context.is_authorized() { if !context.is_authorized() {
return Err(ViewError::PermissionDenied); return Err(ViewError::PermissionDenied);
} }
let c = context.db()?; let mut c = context.db()?;
use crate::models::{Person, PhotoPerson}; 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::*; use crate::schema::photo_people::dsl::*;
let q = photo_people let q = photo_people
.filter(photo_id.eq(form.image)) .filter(photo_id.eq(form.image))
.filter(person_id.eq(person.id)); .filter(person_id.eq(person.id));
if q.first::<PhotoPerson>(&c).optional()?.is_some() { if q.first::<PhotoPerson>(&mut c).optional()?.is_some() {
info!("Photo #{} already has {:?}", form.image, person); info!("Photo #{} already has {:?}", form.image, person);
} else { } else {
info!("Add {:?} on photo #{}!", person, form.image); info!("Add {:?} on photo #{}!", person, form.image);
diesel::insert_into(photo_people) diesel::insert_into(photo_people)
.values((photo_id.eq(form.image), person_id.eq(person.id))) .values((photo_id.eq(form.image), person_id.eq(person.id)))
.execute(&c)?; .execute(&mut c)?;
} }
Ok(redirect_to_img(form.image)) Ok(redirect_to_img(form.image))
} }
@ -132,7 +132,7 @@ async fn set_grade(context: Context, form: GradeForm) -> Result<Response> {
use crate::schema::photos::dsl::{grade, photos}; use crate::schema::photos::dsl::{grade, photos};
let q = let q =
diesel::update(photos.find(form.image)).set(grade.eq(form.grade)); diesel::update(photos.find(form.image)).set(grade.eq(form.grade));
match q.execute(&context.db()?)? { match q.execute(&mut context.db()?)? {
1 => { 1 => {
return Ok(redirect_to_img(form.image)); return Ok(redirect_to_img(form.image));
} }
@ -173,7 +173,7 @@ async fn set_location(context: Context, form: CoordForm) -> Result<Response> {
.on_conflict(photo_id) .on_conflict(photo_id)
.do_update() .do_update()
.set((latitude.eq(lat), longitude.eq(lng))) .set((latitude.eq(lat), longitude.eq(lng)))
.execute(&context.db()?)?; .execute(&mut context.db()?)?;
match context match context
.overpass() .overpass()
.update_image_places(&context.db_pool(), form.image) .update_image_places(&context.db_pool(), form.image)

View File

@ -60,9 +60,9 @@ fn w<T: Serialize>(result: ApiResult<T>) -> Response {
} }
fn login(context: Context, form: LoginForm) -> ApiResult<LoginOk> { fn login(context: Context, form: LoginForm) -> ApiResult<LoginOk> {
let db = context.db()?; let mut db = context.db()?;
let user = form let user = form
.validate(&db) .validate(&mut db)
.ok_or_else(|| ApiError::bad_request("login failed"))?; .ok_or_else(|| ApiError::bad_request("login failed"))?;
tracing::info!("Api login {user:?} ok"); tracing::info!("Api login {user:?} ok");
Ok(LoginOk { Ok(LoginOk {
@ -98,7 +98,7 @@ enum ImgIdentifier {
} }
impl ImgIdentifier { impl ImgIdentifier {
fn load(&self, db: &PgConnection) -> Result<Option<Photo>, DbError> { fn load(&self, db: &mut PgConnection) -> Result<Option<Photo>, DbError> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
match &self { match &self {
ImgIdentifier::Id(ref id) => { ImgIdentifier::Id(ref id) => {
@ -114,8 +114,8 @@ impl ImgIdentifier {
fn get_img(context: Context, q: ImgQuery) -> ApiResult<GetImgResult> { fn get_img(context: Context, q: ImgQuery) -> ApiResult<GetImgResult> {
let id = q.validate().map_err(ApiError::bad_request)?; let id = q.validate().map_err(ApiError::bad_request)?;
let db = context.db()?; let mut db = context.db()?;
let img = id.load(&db)?.ok_or(NOT_FOUND)?; let img = id.load(&mut db)?.ok_or(NOT_FOUND)?;
if !context.is_authorized() && !img.is_public() { if !context.is_authorized() && !img.is_public() {
return Err(NOT_FOUND); return Err(NOT_FOUND);
} }
@ -130,12 +130,12 @@ fn make_public(context: Context, q: ImgQuery) -> ApiResult<GetImgResult> {
}); });
} }
let id = q.validate().map_err(ApiError::bad_request)?; let id = q.validate().map_err(ApiError::bad_request)?;
let db = context.db()?; let mut db = context.db()?;
let img = id.load(&db)?.ok_or(NOT_FOUND)?; let img = id.load(&mut db)?.ok_or(NOT_FOUND)?;
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
let img = update(p::photos.find(img.id)) let img = update(p::photos.find(img.id))
.set(p::is_public.eq(true)) .set(p::is_public.eq(true))
.get_result(&db)?; .get_result(&mut db)?;
Ok(GetImgResult::for_img(&img)) Ok(GetImgResult::for_img(&img))
} }

View File

@ -7,7 +7,7 @@ use crate::schema::photos::dsl as p;
use crate::schema::places::dsl as l; use crate::schema::places::dsl as l;
use crate::schema::tags::dsl as t; use crate::schema::tags::dsl as t;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sql_types::{Integer, Text}; use diesel::sql_types::Text;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::Display; use std::fmt::Display;
@ -160,8 +160,8 @@ fn select_tags(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)),
))) )))
}; };
let db = context.db()?; let mut db = context.db()?;
Ok(query.order((tpos, t::tag_name)).limit(10).load(&db)?) Ok(query.order((tpos, t::tag_name)).limit(10).load(&mut db)?)
} }
fn select_people(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> { fn select_people(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
@ -182,8 +182,11 @@ fn select_people(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
), ),
) )
}; };
let db = context.db()?; let mut db = context.db()?;
Ok(query.order((ppos, h::person_name)).limit(10).load(&db)?) Ok(query
.order((ppos, h::person_name))
.limit(10)
.load(&mut db)?)
} }
fn select_places(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> { fn select_places(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
@ -204,6 +207,6 @@ fn select_places(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
), ),
) )
}; };
let db = context.db()?; let mut db = context.db()?;
Ok(query.order((lpos, l::place_name)).limit(10).load(&db)?) Ok(query.order((lpos, l::place_name)).limit(10).load(&mut db)?)
} }

View File

@ -10,7 +10,7 @@ use warp::reply::Response;
pub async fn show_image(img: ImgName, context: Context) -> Result<Response> { pub async fn show_image(img: ImgName, context: Context) -> Result<Response> {
use crate::schema::photos::dsl::photos; use crate::schema::photos::dsl::photos;
let tphoto = photos.find(img.id).first::<Photo>(&context.db()?); let tphoto = photos.find(img.id).first::<Photo>(&mut context.db()?);
if let Ok(tphoto) = tphoto { if let Ok(tphoto) = tphoto {
if context.is_authorized() || tphoto.is_public() { if context.is_authorized() || tphoto.is_public() {
if img.size == SizeTag::Large { if img.size == SizeTag::Large {

View File

@ -1,5 +1,6 @@
use super::{wrap, BuilderExt, Context, ContextFilter, RenderRucte, Result}; use super::{wrap, BuilderExt, Context, ContextFilter, RenderRucte, Result};
use crate::templates; use crate::templates;
use diesel::pg::PgConnection;
use diesel::prelude::*; use diesel::prelude::*;
use lazy_regex::regex_is_match; use lazy_regex::regex_is_match;
use serde::Deserialize; use serde::Deserialize;
@ -36,7 +37,8 @@ struct NextQ {
fn post_login(context: Context, form: LoginForm) -> Result<Response> { fn post_login(context: Context, form: LoginForm) -> Result<Response> {
let next = sanitize_next(form.next.as_ref().map(AsRef::as_ref)); 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)?; let token = context.make_token(&user)?;
return Ok(Builder::new() return Ok(Builder::new()
.header( .header(
@ -61,7 +63,7 @@ pub struct LoginForm {
impl LoginForm { impl LoginForm {
/// Retur user if and only if password is correct for user. /// Retur user if and only if password is correct for user.
pub fn validate(&self, db: &PgConnection) -> Option<String> { pub fn validate(&self, db: &mut PgConnection) -> Option<String> {
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;
if let Ok(hash) = users if let Ok(hash) = users
.filter(username.eq(&self.user)) .filter(username.eq(&self.user))

View File

@ -141,21 +141,21 @@ async fn static_file(name: Tail) -> Result<Response> {
fn random_image(context: Context) -> Result<Response> { fn random_image(context: Context) -> Result<Response> {
use crate::schema::photos::dsl::id; use crate::schema::photos::dsl::id;
use diesel::expression::dsl::sql; use diesel::dsl::sql;
use diesel::sql_types::Integer; use diesel::sql_types::Integer;
let photo = Photo::query(context.is_authorized()) let photo = Photo::query(context.is_authorized())
.select(id) .select(id)
.limit(1) .limit(1)
.order(sql::<Integer>("random()")) .order(sql::<Integer>("random()"))
.first(&context.db()?)?; .first(&mut context.db()?)?;
info!("Random: {:?}", photo); info!("Random: {:?}", photo);
Ok(redirect_to_img(photo)) Ok(redirect_to_img(photo))
} }
fn photo_details(id: i32, context: Context) -> Result<Response> { fn photo_details(id: i32, context: Context) -> Result<Response> {
let c = context.db()?; let mut c = context.db()?;
let photo = or_404q!(PhotoDetails::load(id, &c), context); let photo = or_404q!(PhotoDetails::load(id, &mut c), context);
if context.is_authorized() || photo.is_public() { if context.is_authorized() || photo.is_public() {
Ok(Builder::new().html(|o| { Ok(Builder::new().html(|o| {

View File

@ -19,8 +19,8 @@ pub fn search(
context: Context, context: Context,
query: Vec<(String, String)>, query: Vec<(String, String)>,
) -> Result<Response> { ) -> Result<Response> {
let db = context.db()?; let mut db = context.db()?;
let query = SearchQuery::load(query, &db)?; let query = SearchQuery::load(query, &mut db)?;
let mut photos = Photo::query(context.is_authorized()); let mut photos = Photo::query(context.is_authorized());
if let Some(since) = query.since.as_ref() { if let Some(since) = query.since.as_ref() {
@ -71,10 +71,10 @@ pub fn search(
let photos = photos let photos = photos
.order((p::date.desc().nulls_last(), p::id.desc())) .order((p::date.desc().nulls_last(), p::id.desc()))
.load(&db)?; .load(&mut db)?;
let n = photos.len(); 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); let links = split_to_group_links(&photos, &query.to_base_url(), true);
Ok(Builder::new().html(|o| { Ok(Builder::new().html(|o| {
@ -104,7 +104,7 @@ pub struct Filter<T> {
} }
impl<T: Facet> Filter<T> { impl<T: Facet> Filter<T> {
fn load(val: &str, db: &PgConnection) -> Option<Filter<T>> { fn load(val: &str, db: &mut PgConnection) -> Option<Filter<T>> {
let (inc, slug) = match val.strip_prefix('!') { let (inc, slug) = match val.strip_prefix('!') {
Some(val) => (false, val), Some(val) => (false, val),
None => (true, val), None => (true, val),
@ -120,7 +120,10 @@ impl<T: Facet> Filter<T> {
} }
impl SearchQuery { impl SearchQuery {
fn load(query: Vec<(String, String)>, db: &PgConnection) -> Result<Self> { fn load(
query: Vec<(String, String)>,
db: &mut PgConnection,
) -> Result<Self> {
let mut result = SearchQuery::default(); let mut result = SearchQuery::default();
let (mut s_d, mut s_t, mut u_d, mut u_t) = (None, None, None, None); let (mut s_d, mut s_t, mut u_d, mut u_t) = (None, None, None, None);
for (key, val) in &query { for (key, val) in &query {
@ -218,7 +221,7 @@ impl QueryDateTime {
let until_midnight = NaiveTime::from_hms_milli(23, 59, 59, 999); let until_midnight = NaiveTime::from_hms_milli(23, 59, 59, 999);
QueryDateTime::new(datetime_from_parts(date, time, until_midnight)) QueryDateTime::new(datetime_from_parts(date, time, until_midnight))
} }
fn from_img(photo_id: i32, db: &PgConnection) -> Result<Self> { fn from_img(photo_id: i32, db: &mut PgConnection) -> Result<Self> {
Ok(QueryDateTime::new( Ok(QueryDateTime::new(
p::photos p::photos
.select(p::date) .select(p::date)

View File

@ -13,26 +13,27 @@ pub fn links_by_time(
range: ImgRange, range: ImgRange,
with_date: bool, with_date: bool,
) -> Result<(Vec<PhotoLink>, Vec<(Coord, i32)>)> { ) -> Result<(Vec<PhotoLink>, Vec<(Coord, i32)>)> {
let c = context.db()?; let mut c = context.db()?;
use crate::schema::photos::dsl::{date, id}; use crate::schema::photos::dsl::{date, id};
let photos = 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)) photos.filter(date.ge(from_date))
} else { } else {
photos photos
}; };
let photos = if let Some(to_date) = range.to.map(|i| date_of_img(&c, i)) { let photos =
photos.filter(date.le(to_date)) if let Some(to_date) = range.to.map(|i| date_of_img(&mut c, i)) {
} else { photos.filter(date.le(to_date))
photos } else {
}; photos
};
let photos = photos let photos = photos
.order((date.desc().nulls_last(), id.desc())) .order((date.desc().nulls_last(), id.desc()))
.load(&c)?; .load(&mut c)?;
let baseurl = UrlString::new(context.path_without_query()); let baseurl = UrlString::new(context.path_without_query());
Ok(( Ok((
split_to_group_links(&photos, &baseurl, with_date), 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( pub fn get_positions(
photos: &[Photo], photos: &[Photo],
c: &PgConnection, c: &mut PgConnection,
) -> Result<Vec<(Coord, i32)>> { ) -> Result<Vec<(Coord, i32)>> {
use crate::schema::positions::dsl::*; use crate::schema::positions::dsl::*;
Ok(positions Ok(positions

View File

@ -57,7 +57,7 @@ fn person_all(context: Context) -> Result<Response> {
pp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), 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))?) Ok(Builder::new().html(|o| templates::people(o, &context, &images))?)
} }
@ -67,9 +67,11 @@ fn person_one(
context: Context, context: Context,
) -> Result<Response> { ) -> Result<Response> {
use crate::schema::people::dsl::{people, slug}; use crate::schema::people::dsl::{people, slug};
let c = context.db()?; let mut c = context.db()?;
let person = let person = or_404q!(
or_404q!(people.filter(slug.eq(tslug)).first::<Person>(&c), context); people.filter(slug.eq(tslug)).first::<Person>(&mut c),
context
);
use crate::schema::photo_people::dsl::{ use crate::schema::photo_people::dsl::{
person_id, photo_id, photo_people, person_id, photo_id, photo_people,
}; };
@ -98,7 +100,7 @@ fn tag_all(context: Context) -> Result<Response> {
tp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), 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))?) Ok(Builder::new().html(|o| templates::tags(o, &context, &taggs))?)
} }
@ -109,7 +111,7 @@ fn tag_one(
) -> Result<Response> { ) -> Result<Response> {
use crate::schema::tags::dsl::{slug, tags}; use crate::schema::tags::dsl::{slug, tags};
let tag = or_404q!( let tag = or_404q!(
tags.filter(slug.eq(tslug)).first::<Tag>(&context.db()?), tags.filter(slug.eq(tslug)).first::<Tag>(&mut context.db()?),
context context
); );
@ -135,7 +137,7 @@ fn place_all(context: Context) -> Result<Response> {
pp::photo_id.eq_any(p::photos.select(p::id).filter(p::is_public)), 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))?) Ok(Builder::new().html(|o| templates::places(o, &context, &found))?)
} }
@ -146,7 +148,9 @@ fn place_one(
) -> Result<Response> { ) -> Result<Response> {
use crate::schema::places::dsl::{places, slug}; use crate::schema::places::dsl::{places, slug};
let place = or_404q!( let place = or_404q!(
places.filter(slug.eq(tslug)).first::<Place>(&context.db()?), places
.filter(slug.eq(tslug))
.first::<Place>(&mut context.db()?),
context context
); );

View File

@ -7,9 +7,9 @@ use crate::models::{Photo, SizeTag};
use crate::templates::{self, RenderRucte}; use crate::templates::{self, RenderRucte};
use chrono::naive::{NaiveDate, NaiveDateTime}; use chrono::naive::{NaiveDate, NaiveDateTime};
use chrono::{Datelike, Duration, Local}; use chrono::{Datelike, Duration, Local};
use diesel::dsl::sql; use diesel::dsl::count_star;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::sql_types::{BigInt, Integer, Nullable}; use diesel::sql_types::{Bool, Nullable, Timestamp};
use serde::Deserialize; use serde::Deserialize;
use warp::filters::BoxedFilter; use warp::filters::BoxedFilter;
use warp::http::response::Builder; use warp::http::response::Builder;
@ -74,18 +74,47 @@ pub fn routes(s: ContextFilter) -> BoxedFilter<(Response,)> {
.boxed() .boxed()
} }
sql_function! {
#[aggregate]
fn year_of_timestamp(date: Nullable<Timestamp>) -> Nullable<SmallInt>
}
sql_function! {
#[aggregate]
fn month_of_timestamp(date: Nullable<Timestamp>) -> Nullable<SmallInt>
}
sql_function! {
#[aggregate]
fn day_of_timestamp(date: Nullable<Timestamp>) -> Nullable<SmallInt>
}
mod filter {
use diesel::sql_types::{Nullable, Timestamp};
sql_function! {
fn year_of_timestamp(date: Nullable<Timestamp>) -> Nullable<SmallInt>
}
sql_function! {
fn month_of_timestamp(date: Nullable<Timestamp>) -> Nullable<SmallInt>
}
sql_function! {
fn day_of_timestamp(date: Nullable<Timestamp>) -> Nullable<SmallInt>
}
}
fn all_years(context: Context) -> Result<Response> { fn all_years(context: Context) -> Result<Response> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
let db = context.db()?; let mut db = context.db()?;
let groups = Photo::query(context.is_authorized()) let y = year_of_timestamp(p::date);
.select(sql::<(Nullable<Integer>, BigInt)>( let groups = p::photos
"cast(extract(year from date) as int) y, count(*)", .filter(p::path.not_like("%.CR2"))
)) .filter(p::path.not_like("%.dng"))
.group_by(sql::<Nullable<Integer>>("y")) .filter(p::is_public.or::<_, Bool>(context.is_authorized()))
.order(sql::<Nullable<Integer>>("y").desc().nulls_last()) .select((y, count_star()))
.load::<(Option<i32>, i64)>(&db)? .group_by(y)
.order(y.desc().nulls_last())
.load::<(Option<i16>, i64)>(&mut db)?
.iter() .iter()
.map(|&(year, count)| { .map(|&(year, count)| {
let year: Option<i32> = year.map(|y| y as i32);
let q = Photo::query(context.is_authorized()) let q = Photo::query(context.is_authorized())
.order((p::grade.desc().nulls_last(), p::date.asc())) .order((p::grade.desc().nulls_last(), p::date.asc()))
.limit(1); .limit(1);
@ -95,7 +124,7 @@ fn all_years(context: Context) -> Result<Response> {
} else { } else {
q.filter(p::date.is_null()) q.filter(p::date.is_null())
}; };
let photo = photo.first::<Photo>(&db)?; let photo = photo.first::<Photo>(&mut db)?;
Ok(PhotoLink { Ok(PhotoLink {
title: Some( title: Some(
year.map(|y| format!("{}", y)) year.map(|y| format!("{}", y))
@ -122,25 +151,27 @@ fn months_in_year(year: i32, context: Context) -> Result<Response> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
let title: String = format!("Photos from {}", year); let title: String = format!("Photos from {}", year);
let db = context.db()?; let mut db = context.db()?;
let groups = Photo::query(context.is_authorized()) 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.ge(start_of_year(year)))
.filter(p::date.lt(start_of_year(year + 1))) .filter(p::date.lt(start_of_year(year + 1)))
.select(sql::<(Integer, BigInt)>( .select((m, count_star()))
"cast(extract(month from date) as int) m, count(*)", .group_by(m)
)) .order(m.desc().nulls_last())
.group_by(sql::<Integer>("m")) .load::<(Option<i16>, i64)>(&mut db)?
.order(sql::<Integer>("m").desc().nulls_last())
.load::<(i32, i64)>(&db)?
.iter() .iter()
.map(|&(month, count)| { .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()) let photo = Photo::query(context.is_authorized())
.filter(p::date.ge(start_of_month(year, month))) .filter(p::date.ge(start_of_month(year, month)))
.filter(p::date.lt(start_of_month(year, month + 1))) .filter(p::date.lt(start_of_month(year, month + 1)))
.order((p::grade.desc().nulls_last(), p::date.asc())) .order((p::grade.desc().nulls_last(), p::date.asc()))
.limit(1) .limit(1)
.first::<Photo>(&db)?; .first::<Photo>(&mut db)?;
Ok(PhotoLink { Ok(PhotoLink {
title: Some(monthname(month).to_string()), title: Some(monthname(month).to_string()),
@ -163,7 +194,7 @@ fn months_in_year(year: i32, context: Context) -> Result<Response> {
.filter(p::date.ge(start_of_year(year))) .filter(p::date.ge(start_of_year(year)))
.filter(p::date.lt(start_of_year(year + 1))) .filter(p::date.lt(start_of_year(year + 1)))
.select((photo_id, latitude, longitude)) .select((photo_id, latitude, longitude))
.load(&db)? .load(&mut db)?
.into_iter() .into_iter()
.map(|(p_id, lat, long): (i32, i32, i32)| { .map(|(p_id, lat, long): (i32, i32, i32)| {
((lat, long).into(), p_id) ((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<Response> { fn days_in_month(year: i32, month: u32, context: Context) -> Result<Response> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
let d = day_of_timestamp(p::date);
let lpath: Vec<Link> = vec![Link::year(year)]; let lpath: Vec<Link> = vec![Link::year(year)];
let title: String = format!("Photos from {} {}", monthname(month), year); let title: String = format!("Photos from {} {}", monthname(month), year);
let db = context.db()?; let mut db = context.db()?;
let groups = Photo::query(context.is_authorized()) 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.ge(start_of_month(year, month)))
.filter(p::date.lt(start_of_month(year, month + 1))) .filter(p::date.lt(start_of_month(year, month + 1)))
.select(sql::<(Integer, BigInt)>( .select((d, count_star()))
"cast(extract(day from date) as int) d, count(*)", .group_by(d)
)) .order(d.desc().nulls_last())
.group_by(sql::<Integer>("d")) .load::<(Option<i16>, i64)>(&mut db)?
.order(sql::<Integer>("d").desc().nulls_last())
.load::<(i32, i64)>(&db)?
.iter() .iter()
.map(|&(day, count)| { .map(|&(day, count)| {
let day = day as u32; let day = day.unwrap() as u32;
let fromdate = let fromdate =
NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0); NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0);
let photo = Photo::query(context.is_authorized()) let photo = Photo::query(context.is_authorized())
@ -209,7 +242,7 @@ fn days_in_month(year: i32, month: u32, context: Context) -> Result<Response> {
.filter(p::date.lt(fromdate + Duration::days(1))) .filter(p::date.lt(fromdate + Duration::days(1)))
.order((p::grade.desc().nulls_last(), p::date.asc())) .order((p::grade.desc().nulls_last(), p::date.asc()))
.limit(1) .limit(1)
.first::<Photo>(&db)?; .first::<Photo>(&mut db)?;
Ok(PhotoLink { Ok(PhotoLink {
title: Some(format!("{}", day)), title: Some(format!("{}", day)),
@ -230,7 +263,7 @@ fn days_in_month(year: i32, month: u32, context: Context) -> Result<Response> {
.filter(p::date.ge(start_of_month(year, month))) .filter(p::date.ge(start_of_month(year, month)))
.filter(p::date.lt(start_of_month(year, month + 1))) .filter(p::date.lt(start_of_month(year, month + 1)))
.select((ps::photo_id, ps::latitude, ps::longitude)) .select((ps::photo_id, ps::latitude, ps::longitude))
.load(&db)? .load(&mut db)?
.into_iter() .into_iter()
.map(|(p_id, lat, long): (i32, i32, i32)| { .map(|(p_id, lat, long): (i32, i32, i32)| {
((lat, long).into(), p_id) ((lat, long).into(), p_id)
@ -248,7 +281,7 @@ fn all_null_date(context: Context) -> Result<Response> {
.filter(p::date.is_null()) .filter(p::date.is_null())
.order(p::path.asc()) .order(p::path.asc())
.limit(500) .limit(500)
.load(&context.db()?)? .load(&mut context.db()?)?
.iter() .iter()
.map(PhotoLink::no_title) .map(PhotoLink::no_title)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -303,40 +336,39 @@ fn on_this_day(context: Context) -> Result<Response> {
let today = Local::now(); let today = Local::now();
(today.month(), today.day()) (today.month(), today.day())
}; };
let db = context.db()?; let mut db = context.db()?;
let pos = Photo::query(context.is_authorized()) let pos = Photo::query(context.is_authorized())
.inner_join(ps::positions) .inner_join(ps::positions)
.filter( .filter(filter::month_of_timestamp(p::date).eq(month as i16))
sql("extract(month from date)=").bind::<Integer, _>(month as i32), .filter(filter::day_of_timestamp(p::date).eq(day as i16))
)
.filter(sql("extract(day from date)=").bind::<Integer, _>(day as i32))
.select((ps::photo_id, ps::latitude, ps::longitude)) .select((ps::photo_id, ps::latitude, ps::longitude))
.load(&db)? .load(&mut db)?
.into_iter() .into_iter()
.map(|(p_id, lat, long): (i32, i32, i32)| ((lat, long).into(), p_id)) .map(|(p_id, lat, long): (i32, i32, i32)| ((lat, long).into(), p_id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let photos = Photo::query(context.is_authorized()) let y = year_of_timestamp(p::date);
.select(sql::<(Integer, BigInt)>( let photos = p::photos
"cast(extract(year from date) as int) y, count(*)", .filter(p::path.not_like("%.CR2"))
)) .filter(p::path.not_like("%.dng"))
.group_by(sql::<Integer>("y")) .filter(p::is_public.or::<_, Bool>(context.is_authorized()))
.filter( .filter(filter::month_of_timestamp(p::date).eq(month as i16))
sql("extract(month from date)=").bind::<Integer, _>(month as i32), .filter(filter::day_of_timestamp(p::date).eq(day as i16))
) .select((y, count_star()))
.filter(sql("extract(day from date)=").bind::<Integer, _>(day as i32)) .group_by(y)
.order(sql::<Integer>("y").desc()) .order(y.desc())
.load::<(i32, i64)>(&db)? .load::<(Option<i16>, i64)>(&mut db)?
.iter() .iter()
.map(|&(year, count)| { .map(|&(year, count)| {
let year = year.unwrap(); // matching date can't be null
let fromdate = 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()) let photo = Photo::query(context.is_authorized())
.filter(p::date.ge(fromdate)) .filter(p::date.ge(fromdate))
.filter(p::date.lt(fromdate + Duration::days(1))) .filter(p::date.lt(fromdate + Duration::days(1)))
.order((p::grade.desc().nulls_last(), p::date.asc())) .order((p::grade.desc().nulls_last(), p::date.asc()))
.limit(1) .limit(1)
.first::<Photo>(&db)?; .first::<Photo>(&mut db)?;
Ok(PhotoLink { Ok(PhotoLink {
title: Some(format!("{}", year)), title: Some(format!("{}", year)),
href: format!("/{}/{}/{}", year, month, day), href: format!("/{}/{}/{}", year, month, day),
@ -360,8 +392,8 @@ fn on_this_day(context: Context) -> Result<Response> {
fn next_image(context: Context, param: FromParam) -> Result<Response> { fn next_image(context: Context, param: FromParam) -> Result<Response> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
let db = context.db()?; let mut db = context.db()?;
let from_date = or_404!(date_of_img(&db, param.from), context); let from_date = or_404!(date_of_img(&mut db, param.from), context);
let photo = or_404q!( let photo = or_404q!(
Photo::query(context.is_authorized()) Photo::query(context.is_authorized())
.select(p::id) .select(p::id)
@ -371,7 +403,7 @@ fn next_image(context: Context, param: FromParam) -> Result<Response> {
.or(p::date.eq(from_date).and(p::id.gt(param.from))), .or(p::date.eq(from_date).and(p::id.gt(param.from))),
) )
.order((p::date, p::id)) .order((p::date, p::id))
.first::<i32>(&db), .first::<i32>(&mut db),
context context
); );
Ok(redirect_to_img(photo)) Ok(redirect_to_img(photo))
@ -379,8 +411,8 @@ fn next_image(context: Context, param: FromParam) -> Result<Response> {
fn prev_image(context: Context, param: FromParam) -> Result<Response> { fn prev_image(context: Context, param: FromParam) -> Result<Response> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
let db = context.db()?; let mut db = context.db()?;
let from_date = or_404!(date_of_img(&db, param.from), context); let from_date = or_404!(date_of_img(&mut db, param.from), context);
let photo = or_404q!( let photo = or_404q!(
Photo::query(context.is_authorized()) Photo::query(context.is_authorized())
.select(p::id) .select(p::id)
@ -390,7 +422,7 @@ fn prev_image(context: Context, param: FromParam) -> Result<Response> {
.or(p::date.eq(from_date).and(p::id.lt(param.from))), .or(p::date.eq(from_date).and(p::id.lt(param.from))),
) )
.order((p::date.desc().nulls_last(), p::id.desc())) .order((p::date.desc().nulls_last(), p::id.desc()))
.first::<i32>(&db), .first::<i32>(&mut db),
context context
); );
Ok(redirect_to_img(photo)) Ok(redirect_to_img(photo))
@ -401,7 +433,10 @@ struct FromParam {
from: i32, from: i32,
} }
pub fn date_of_img(db: &PgConnection, photo_id: i32) -> Option<NaiveDateTime> { pub fn date_of_img(
db: &mut PgConnection,
photo_id: i32,
) -> Option<NaiveDateTime> {
use crate::schema::photos::dsl as p; use crate::schema::photos::dsl as p;
p::photos p::photos
.find(photo_id) .find(photo_id)