album/src/main.rs

494 lines
18 KiB
Rust
Raw Normal View History

2016-05-04 14:16:20 +03:00
#[macro_use]
extern crate nickel;
#[macro_use]
extern crate log;
extern crate env_logger;
extern crate nickel_jwt_session;
2015-11-21 18:21:44 +03:00
extern crate rustorm;
extern crate rustc_serialize;
2015-11-22 15:01:39 +03:00
extern crate typemap;
extern crate plugin;
extern crate image;
extern crate hyper;
extern crate time;
2015-12-22 20:22:21 +03:00
extern crate chrono;
2016-01-31 16:34:48 +03:00
extern crate rexif;
2015-12-25 20:32:11 +03:00
use chrono::UTC;
use chrono::offset::TimeZone;
2016-05-04 14:16:20 +03:00
use chrono::Duration as ChDuration;
2015-12-25 20:32:11 +03:00
use chrono::Datelike;
2015-11-22 15:01:39 +03:00
use hyper::header::{Expires, HttpDate};
use nickel::{FormBody, HttpRouter, MediaType, MiddlewareResult, Nickel,
Request, Response, StaticFilesHandler};
use nickel::extensions::response::Redirect;
2016-05-04 14:16:20 +03:00
use nickel_jwt_session::{SessionMiddleware, SessionRequestExtensions,
SessionResponseExtensions};
use plugin::Pluggable;
use rustc_serialize::Encodable;
use rustorm::query::{Filter, Query};
use time::Duration;
use nickel::status::StatusCode;
2015-11-21 18:21:44 +03:00
2015-12-25 20:32:11 +03:00
mod models;
use models::{Entity, Person, Photo, PhotoQuery, Place, Tag, query_for};
2015-12-25 20:32:11 +03:00
mod env;
use env::{dburl, env_or, jwt_key, photos_dir};
2015-11-21 18:21:44 +03:00
2015-12-26 22:18:44 +03:00
mod photosdir;
mod rustormmiddleware;
use rustormmiddleware::{RustormMiddleware, RustormRequestExtensions};
mod requestloggermiddleware;
use requestloggermiddleware::RequestLoggerMiddleware;
mod photosdirmiddleware;
use photosdirmiddleware::{PhotosDirMiddleware, PhotosDirRequestExtensions};
macro_rules! render {
($res:expr, $template:expr, { $($param:ident : $ptype:ty = $value:expr),* })
=>
{
{
#[derive(Debug, Clone, RustcEncodable)]
struct ParamData {
csslink: String,
$(
$param: $ptype,
)*
}
$res.render($template, &ParamData {
csslink: include!(concat!(env!("OUT_DIR"), "/stylelink")).into(),
$(
$param: $value,
)*
})
}
}
}
2016-05-04 14:16:20 +03:00
fn orm_get_related<T: Entity, Src: Entity>(src: &Src,
rel_table: &str)
-> Query {
2015-12-22 20:22:21 +03:00
let mut q = Query::select();
q.only_from(&T::table());
2016-05-04 14:16:20 +03:00
q.left_join_table(rel_table,
&format!("{}.id", T::table().name),
2015-12-22 20:22:21 +03:00
&format!("{}.{}", rel_table, T::table().name))
2016-05-04 14:16:20 +03:00
.filter_eq(&format!("{}.{}", rel_table, Src::table().name), src.id());
2015-12-22 20:22:21 +03:00
q
}
2016-01-31 23:15:44 +03:00
#[derive(Debug, Clone, RustcEncodable)]
struct Group {
title: String,
url: String,
count: i64,
photo: Photo,
}
fn monthname(n: u8) -> &'static str {
match n {
1 => "january",
2 => "february",
3 => "march",
4 => "april",
5 => "may",
6 => "june",
7 => "july",
8 => "august",
9 => "september",
10 => "october",
11 => "november",
12 => "december",
2016-05-04 14:16:20 +03:00
_ => "non-month",
2016-01-31 23:15:44 +03:00
}
}
fn main() {
env_logger::init().unwrap();
2015-11-21 18:21:44 +03:00
info!("Initalized logger");
let mut server = Nickel::new();
server.utilize(RequestLoggerMiddleware);
server.utilize(SessionMiddleware::new(&jwt_key()));
// TODO This is a "build" location, not an "install" location ...
let staticdir = concat!(env!("OUT_DIR"), "/static/");
info!("Serving static files from {}", staticdir);
server.utilize(StaticFilesHandler::new(staticdir));
2015-11-22 15:01:39 +03:00
server.utilize(RustormMiddleware::new(&dburl()));
server.utilize(PhotosDirMiddleware::new(photos_dir()));
server.get("/login", login);
server.post("/login", do_login);
server.get("/logout", logout);
server.get("/", all_years);
server.get("/img/:id/:size", show_image);
server.get("/tag/", tag_all);
server.get("/tag/:tag", tag_one);
server.get("/place/", place_all);
server.get("/place/:slug", place_one);
server.get("/person/", person_all);
server.get("/person/:slug", person_one);
server.get("/details/:id", photo_details);
server.get("/:year/", months_in_year);
server.get("/:year/:month/", days_in_month);
server.get("/:year/:month/:day", all_for_day);
server.listen(&*env_or("RPHOTOS_LISTEN", "127.0.0.1:6767"));
}
2016-05-04 14:16:20 +03:00
fn login<'mw>(_req: &mut Request,
mut res: Response<'mw>)
-> MiddlewareResult<'mw> {
res.clear_jwt_user();
render!(res, "templates/login.tpl", {})
}
2016-05-04 14:16:20 +03:00
fn do_login<'mw>(req: &mut Request,
mut res: Response<'mw>)
-> MiddlewareResult<'mw> {
let form_data = try_with!(res, req.form_body());
if let (Some(user), Some(password)) = (form_data.get("user"),
form_data.get("password")) {
// TODO Actual password hashing and checking
if user == "kaj" && password == "kaj123" {
res.set_jwt_user(user);
return res.redirect("/");
}
}
render!(res, "templates/login.tpl", {})
}
2016-05-04 14:16:20 +03:00
fn logout<'mw>(_req: &mut Request,
mut res: Response<'mw>)
-> MiddlewareResult<'mw> {
res.clear_jwt_user();
res.redirect("/")
}
2016-05-04 14:16:20 +03:00
fn show_image<'mw>(req: &mut Request,
mut res: Response<'mw>)
-> MiddlewareResult<'mw> {
if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
if let Ok(photo) = req.orm_get::<Photo>("id", &id) {
if req.authorized_user().is_some() || photo.is_public() {
if let Some(size) = match req.param("size").unwrap() {
"s" => Some(200),
"m" => Some(800),
"l" => Some(1200),
2016-05-04 14:16:20 +03:00
_ => None,
} {
match req.photos().get_scaled_image(photo, size, size) {
Ok(buf) => {
res.set(MediaType::Jpeg);
2016-05-04 14:16:20 +03:00
res.set(Expires(HttpDate(time::now() +
Duration::days(14))));
return res.send(buf);
2016-05-04 14:16:20 +03:00
}
Err(err) => {
return res.error(StatusCode::InternalServerError,
format!("{}", err));
}
}
}
2015-11-28 11:53:12 +03:00
}
}
}
res.error(StatusCode::NotFound, "No such image")
}
2016-05-04 14:16:20 +03:00
fn tag_all<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
return render!(res, "templates/tags.tpl", {
user: Option<String> = req.authorized_user(),
tags: Vec<Tag> = query_for::<Tag>().asc("tag")
.collect(req.db_conn()).unwrap()
});
}
2016-05-04 14:16:20 +03:00
fn tag_one<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
let slug = req.param("tag").unwrap();
if let Ok(tag) = req.orm_get::<Tag>("slug", &slug) {
return render!(res, "templates/tag.tpl", {
user: Option<String> = req.authorized_user(),
photos: Vec<Photo> =
orm_get_related::<Photo, Tag>(&tag, "photo_tag")
.only_public(req.authorized_user().is_none())
.desc_nulls_last("grade")
.desc_nulls_last("date")
.collect(req.db_conn()).unwrap(),
tag: Tag = tag
});
}
res.error(StatusCode::NotFound, "Not a tag")
}
2016-05-04 14:16:20 +03:00
fn place_all<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
return render!(res, "templates/places.tpl", {
user: Option<String> = req.authorized_user(),
places: Vec<Place> = query_for::<Place>().asc("place")
.collect(req.db_conn()).unwrap()
});
}
2016-05-04 14:16:20 +03:00
fn place_one<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
let slug = req.param("slug").unwrap();
if let Ok(place) = req.orm_get::<Place>("slug", &slug) {
return render!(res, "templates/place.tpl", {
user: Option<String> = req.authorized_user(),
photos: Vec<Photo> =
orm_get_related::<Photo, Place>(&place, "photo_place")
.only_public(req.authorized_user().is_none())
.desc_nulls_last("grade")
.desc_nulls_last("date")
.collect(req.db_conn()).unwrap(),
place: Place = place
});
}
res.error(StatusCode::NotFound, "Not a place")
}
2016-05-04 14:16:20 +03:00
fn person_all<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
return render!(res, "templates/people.tpl", {
user: Option<String> = req.authorized_user(),
people: Vec<Person> = query_for::<Person>().asc("name")
.collect(req.db_conn()).unwrap()
});
}
2016-05-04 14:16:20 +03:00
fn person_one<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
let slug = req.param("slug").unwrap();
if let Ok(person) = req.orm_get::<Person>("slug", &slug) {
return render!(res, "templates/person.tpl", {
user: Option<String> = req.authorized_user(),
photos: Vec<Photo> =
orm_get_related::<Photo, Person>(&person, "photo_person")
.only_public(req.authorized_user().is_none())
.desc_nulls_last("grade")
.desc_nulls_last("date")
.collect(req.db_conn()).unwrap(),
person: Person = person
});
}
res.error(StatusCode::NotFound, "Not a place")
}
2016-05-04 14:16:20 +03:00
fn photo_details<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
if let Ok(id) = req.param("id").unwrap().parse::<i32>() {
if let Ok(photo) = req.orm_get::<Photo>("id", &id) {
if req.authorized_user().is_some() || photo.is_public() {
2016-05-04 14:16:20 +03:00
return render!(res, "templates/details.tpl", {
user: Option<String> = req.authorized_user(),
lpath: Vec<Link> =
2016-05-04 23:11:31 +03:00
photo.date
.map(|d| vec![Link::year(d.year()),
Link::month(d.year(), d.month() as u8),
Link::day(d.year(), d.month() as u8, d.day())])
.unwrap_or_else(|| vec![]),
people: Vec<Person> =
req.orm_get_related(&photo, "photo_person").unwrap(),
places: Vec<Place> =
req.orm_get_related(&photo, "photo_place").unwrap(),
tags: Vec<Tag> =
req.orm_get_related(&photo, "photo_tag").unwrap(),
time: String = match photo.date {
Some(d) => d.format("%T").to_string(),
None => "".to_string()
},
2016-05-04 19:45:50 +03:00
year: Option<i32> = photo.date.map(|d| d.year()),
month: Option<u32> = photo.date.map(|d| d.month()),
day: Option<u32> = photo.date.map(|d| d.day()),
photo: Photo = photo
2016-05-04 14:16:20 +03:00
});
}
2015-11-28 11:53:12 +03:00
}
}
res.error(StatusCode::NotFound, "Not a year")
}
2016-05-04 14:16:20 +03:00
fn all_years<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
return render!(res, "templates/groups.tpl", {
user: Option<String> = req.authorized_user(),
title: &'static str = "All photos",
groups: Vec<Group> = query_for::<Photo>()
.columns(vec!("extract(year from date) y", "count(*) c"))
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
.add_filter(Filter::is_not_null("date"))
.group_by(vec!("y")).asc("y")
.retrieve(req.db_conn()).expect("Get images per year")
.dao.iter().map(|dao| {
debug!("Got a pregroup: {:?}", dao);
let year = dao.get::<f64>("y") as u16;
let count : i64 = dao.get("c");
let photo : Photo = query_for::<Photo>()
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
2016-05-04 23:11:31 +03:00
.filter_date("date", "year", year as u32)
.desc_nulls_last("grade")
.asc_nulls_last("date")
.limit(1)
.collect_one(req.db_conn()).unwrap();
Group {
title: format!("{}", year),
url: format!("/{}/", year),
count: count,
photo: photo
}
}).collect()
});
}
2016-05-04 14:16:20 +03:00
fn months_in_year<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
if let Ok(year) = req.param("year").unwrap().parse::<i32>() {
return render!(res, "templates/groups.tpl", {
user: Option<String> = req.authorized_user(),
title: String = format!("Photos from {}", year),
groups: Vec<Group> = query_for::<Photo>()
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
.columns(vec!("extract(month from date) m", "count(*) c"))
2016-05-04 23:11:31 +03:00
.filter_date("date", "year", year as u32)
.group_by(vec!("m")).asc("m")
.retrieve(req.db_conn()).expect("Get images per month")
.dao.iter().map(|dao| {
let month = dao.get::<f64>("m") as u8;
let count : i64 = dao.get("c");
let photo : Photo = query_for::<Photo>()
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
2016-05-04 23:11:31 +03:00
.filter_date("date", "year", year as u32)
.filter_date("date", "month", month as u32)
2015-12-22 20:22:21 +03:00
.desc_nulls_last("grade")
.asc_nulls_last("date")
.limit(1)
.collect_one(req.db_conn()).unwrap();
Group {
title: monthname(month).to_string(),
url: format!("/{}/{}/", year, month),
count: count,
photo: photo
2015-12-06 16:42:03 +03:00
}
}).collect()
});
}
res.error(StatusCode::NotFound, "Not a year")
}
2016-05-04 14:16:20 +03:00
fn days_in_month<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
if let Ok(year) = req.param("year").unwrap().parse::<i32>() {
if let Ok(month) = req.param("month").unwrap().parse::<u8>() {
return render!(res, "templates/groups.tpl", {
user: Option<String> = req.authorized_user(),
lpath: Vec<Link> = vec![Link::year(year)],
title: String = format!("Photos from {} {}", monthname(month),
year),
groups: Vec<Group> = query_for::<Photo>()
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
.columns(vec!("extract(day from date) d", "count(*) c"))
2016-05-04 23:11:31 +03:00
.filter_date("date", "year", year as u32)
.filter_date("date", "month", month as u32)
.group_by(vec!("d")).asc("d")
.retrieve(req.db_conn()).expect("Get images per day")
.dao.iter().map(|dao| {
let day = dao.get::<f64>("d") as u8;
let count : i64 = dao.get("c");
let photo = query_for::<Photo>()
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
2016-05-04 23:11:31 +03:00
.filter_date("date", "year", year as u32)
.filter_date("date", "month", month as u32)
.filter_date("date", "day", day as u32)
.desc_nulls_last("grade")
.asc_nulls_last("date")
.limit(1)
.collect_one(req.db_conn()).unwrap();
Group {
title: format!("{}/{}", day, month),
url: format!("/{}/{}/{}", year, month, day),
count: count,
photo: photo
2016-01-31 23:15:44 +03:00
}
}).collect()
});
2015-12-25 20:32:11 +03:00
}
}
res.error(StatusCode::NotFound, "Not a month")
}
2016-05-04 14:16:20 +03:00
fn all_for_day<'mw>(req: &mut Request,
res: Response<'mw>)
-> MiddlewareResult<'mw> {
if let Ok(year) = req.param("year").unwrap().parse::<i32>() {
if let Ok(month) = req.param("month").unwrap().parse::<u8>() {
if let Ok(day) = req.param("day").unwrap().parse::<u32>() {
2016-05-04 14:16:20 +03:00
let date = UTC.ymd(year, month as u32, day).and_hms(0, 0, 0);
return render!(res, "templates/index.tpl", {
user: Option<String> = req.authorized_user(),
lpath: Vec<Link> = vec![Link::year(year),
Link::month(year, month)],
title: String = format!("Photos from {} {} {}",
day, monthname(month), year),
photos: Vec<Photo> = query_for::<Photo>()
.only_public(req.authorized_user().is_none())
2016-05-15 23:05:42 +03:00
.no_raw()
.filter_gte("date", &date)
.filter_lt("date", &(date + ChDuration::days(1)))
.desc_nulls_last("grade")
.asc_nulls_last("date")
.collect(req.db_conn()).unwrap()
2016-05-04 14:16:20 +03:00
});
2015-12-25 20:32:11 +03:00
}
}
}
res.error(StatusCode::NotFound, "Not a day")
}
2016-05-04 19:45:50 +03:00
#[derive(Debug, Clone, RustcEncodable)]
struct Link {
pub url: String,
pub name: String,
}
impl Link {
fn year(year: i32) -> Self {
Link {
url: format!("/{}/", year),
name: format!("{}", year),
}
}
fn month(year: i32, month: u8) -> Self {
Link {
url: format!("/{}/{}/", year, month),
name: format!("{}", month),
}
}
fn day(year: i32, month: u8, day: u32) -> Self {
Link {
url: format!("/{}/{}/{}", year, month, day),
name: format!("{}", day),
}
}
2016-05-04 19:45:50 +03:00
}