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
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.

View File

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

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 {
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<Option<Camera>, Error> {
if let Some((make, model)) = exif.camera() {

View File

@ -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<In: BufRead + Sized>(
db: &PgConnection,
db: &mut PgConnection,
list: In,
) -> Result<(), Error> {
for line in list.lines() {

View File

@ -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::<Photo>(&self.db.connect()?)?;
.load::<Photo>(&mut self.db.connect()?)?;
let no_expire = 0;
let pd = PhotosDir::new(&self.photos.photos_dir);
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::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::<i64>(db)?,

View File

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

View File

@ -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::<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()
.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::<PhotoPlace>(&c).is_ok() {
if q.first::<PhotoPlace>(&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,

View File

@ -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),

View File

@ -28,7 +28,7 @@ pub struct PhotoDetails {
pub camera: Option<Camera>,
}
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;
let photo = photos.find(id).first::<Photo>(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<Self, Error>
fn by_slug(slug: &str, db: &mut PgConnection) -> Result<Self, Error>
where
Self: Sized;
}
@ -252,7 +252,7 @@ pub struct 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)
}
}
@ -273,7 +273,7 @@ pub struct Person {
impl Person {
pub fn get_or_create_name(
db: &PgConnection,
db: &mut PgConnection,
name: &str,
) -> Result<Person, Error> {
h::people
@ -291,7 +291,7 @@ impl 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)
}
}
@ -313,7 +313,7 @@ pub struct 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)
}
}
@ -334,7 +334,7 @@ pub struct Camera {
impl Camera {
pub fn get_or_create(
db: &PgConnection,
db: &mut PgConnection,
make: &str,
modl: &str,
) -> Result<Camera, Error> {
@ -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<Self> {
Ok(Coord::from((row.0, row.1)))
}
}

View File

@ -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() {

View File

@ -35,8 +35,8 @@ fn rotate(context: Context, form: RotateForm) -> Result<Response> {
}
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::<Photo>(c), context);
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() {
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::<Tag>(&c)
.first::<Tag>(&mut c)
.or_else(|_| {
diesel::insert_into(tags)
.values((
tag_name.eq(&form.tag),
slug.eq(&slugify(&form.tag)),
))
.get_result::<Tag>(&c)
.get_result::<Tag>(&mut c)
})?
};
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(tag_id.eq(tag.id))
.count();
if q.get_result::<i64>(&c)? > 0 {
if q.get_result::<i64>(&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<Response> {
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::<PhotoPerson>(&c).optional()?.is_some() {
if q.first::<PhotoPerson>(&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<Response> {
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<Response> {
.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)

View File

@ -60,9 +60,9 @@ fn w<T: Serialize>(result: ApiResult<T>) -> Response {
}
fn login(context: Context, form: LoginForm) -> ApiResult<LoginOk> {
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<Option<Photo>, DbError> {
fn load(&self, db: &mut PgConnection) -> Result<Option<Photo>, 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<GetImgResult> {
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<GetImgResult> {
});
}
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))
}

View File

@ -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<Vec<NameSlugScore>> {
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<Vec<NameSlugScore>> {
@ -182,8 +182,11 @@ fn select_people(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
),
)
};
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<Vec<NameSlugScore>> {
@ -204,6 +207,6 @@ fn select_places(context: &Context, term: &AcQ) -> Result<Vec<NameSlugScore>> {
),
)
};
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)?)
}

View File

@ -10,7 +10,7 @@ use warp::reply::Response;
pub async fn show_image(img: ImgName, context: Context) -> Result<Response> {
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 context.is_authorized() || tphoto.is_public() {
if img.size == SizeTag::Large {

View File

@ -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<Response> {
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<String> {
pub fn validate(&self, db: &mut PgConnection) -> Option<String> {
use crate::schema::users::dsl::*;
if let Ok(hash) = users
.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> {
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::<Integer>("random()"))
.first(&context.db()?)?;
.first(&mut context.db()?)?;
info!("Random: {:?}", photo);
Ok(redirect_to_img(photo))
}
fn photo_details(id: i32, context: Context) -> Result<Response> {
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| {

View File

@ -19,8 +19,8 @@ pub fn search(
context: Context,
query: Vec<(String, String)>,
) -> Result<Response> {
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<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('!') {
Some(val) => (false, val),
None => (true, val),
@ -120,7 +120,10 @@ impl<T: Facet> Filter<T> {
}
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 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<Self> {
fn from_img(photo_id: i32, db: &mut PgConnection) -> Result<Self> {
Ok(QueryDateTime::new(
p::photos
.select(p::date)

View File

@ -13,26 +13,27 @@ pub fn links_by_time(
range: ImgRange,
with_date: bool,
) -> Result<(Vec<PhotoLink>, 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<Vec<(Coord, i32)>> {
use crate::schema::positions::dsl::*;
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)),
)))
};
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<Response> {
use crate::schema::people::dsl::{people, slug};
let c = context.db()?;
let person =
or_404q!(people.filter(slug.eq(tslug)).first::<Person>(&c), context);
let mut c = context.db()?;
let person = or_404q!(
people.filter(slug.eq(tslug)).first::<Person>(&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<Response> {
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<Response> {
use crate::schema::tags::dsl::{slug, tags};
let tag = or_404q!(
tags.filter(slug.eq(tslug)).first::<Tag>(&context.db()?),
tags.filter(slug.eq(tslug)).first::<Tag>(&mut context.db()?),
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)),
)))
};
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<Response> {
use crate::schema::places::dsl::{places, slug};
let place = or_404q!(
places.filter(slug.eq(tslug)).first::<Place>(&context.db()?),
places
.filter(slug.eq(tslug))
.first::<Place>(&mut context.db()?),
context
);

View File

@ -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<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> {
use crate::schema::photos::dsl as p;
let db = context.db()?;
let groups = Photo::query(context.is_authorized())
.select(sql::<(Nullable<Integer>, BigInt)>(
"cast(extract(year from date) as int) y, count(*)",
))
.group_by(sql::<Nullable<Integer>>("y"))
.order(sql::<Nullable<Integer>>("y").desc().nulls_last())
.load::<(Option<i32>, 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<i16>, i64)>(&mut db)?
.iter()
.map(|&(year, count)| {
let year: Option<i32> = 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<Response> {
} else {
q.filter(p::date.is_null())
};
let photo = photo.first::<Photo>(&db)?;
let photo = photo.first::<Photo>(&mut db)?;
Ok(PhotoLink {
title: Some(
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;
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::<Integer>("m"))
.order(sql::<Integer>("m").desc().nulls_last())
.load::<(i32, i64)>(&db)?
.select((m, count_star()))
.group_by(m)
.order(m.desc().nulls_last())
.load::<(Option<i16>, 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::<Photo>(&db)?;
.first::<Photo>(&mut db)?;
Ok(PhotoLink {
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.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<Response> {
use crate::schema::photos::dsl as p;
let d = day_of_timestamp(p::date);
let lpath: Vec<Link> = 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::<Integer>("d"))
.order(sql::<Integer>("d").desc().nulls_last())
.load::<(i32, i64)>(&db)?
.select((d, count_star()))
.group_by(d)
.order(d.desc().nulls_last())
.load::<(Option<i16>, 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<Response> {
.filter(p::date.lt(fromdate + Duration::days(1)))
.order((p::grade.desc().nulls_last(), p::date.asc()))
.limit(1)
.first::<Photo>(&db)?;
.first::<Photo>(&mut db)?;
Ok(PhotoLink {
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.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<Response> {
.filter(p::date.is_null())
.order(p::path.asc())
.limit(500)
.load(&context.db()?)?
.load(&mut context.db()?)?
.iter()
.map(PhotoLink::no_title)
.collect::<Vec<_>>();
@ -303,40 +336,39 @@ fn on_this_day(context: Context) -> Result<Response> {
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::<Integer, _>(month as i32),
)
.filter(sql("extract(day from date)=").bind::<Integer, _>(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::<Vec<_>>();
let photos = Photo::query(context.is_authorized())
.select(sql::<(Integer, BigInt)>(
"cast(extract(year from date) as int) y, count(*)",
))
.group_by(sql::<Integer>("y"))
.filter(
sql("extract(month from date)=").bind::<Integer, _>(month as i32),
)
.filter(sql("extract(day from date)=").bind::<Integer, _>(day as i32))
.order(sql::<Integer>("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<i16>, 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::<Photo>(&db)?;
.first::<Photo>(&mut db)?;
Ok(PhotoLink {
title: Some(format!("{}", year)),
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> {
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<Response> {
.or(p::date.eq(from_date).and(p::id.gt(param.from))),
)
.order((p::date, p::id))
.first::<i32>(&db),
.first::<i32>(&mut db),
context
);
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> {
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<Response> {
.or(p::date.eq(from_date).and(p::id.lt(param.from))),
)
.order((p::date.desc().nulls_last(), p::id.desc()))
.first::<i32>(&db),
.first::<i32>(&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<NaiveDateTime> {
pub fn date_of_img(
db: &mut PgConnection,
photo_id: i32,
) -> Option<NaiveDateTime> {
use crate::schema::photos::dsl as p;
p::photos
.find(photo_id)