2016-05-04 14:16:20 +03:00
|
|
|
#[macro_use]
|
|
|
|
extern crate nickel;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
2016-07-02 13:50:25 +03:00
|
|
|
extern crate djangohashers;
|
2015-11-20 01:43:04 +03:00
|
|
|
extern crate env_logger;
|
2016-05-01 16:50:49 +03:00
|
|
|
extern crate nickel_jwt_session;
|
2015-11-21 18:21:44 +03:00
|
|
|
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;
|
2016-05-29 22:51:08 +03:00
|
|
|
extern crate rphotos;
|
|
|
|
extern crate r2d2;
|
|
|
|
extern crate nickel_diesel;
|
2016-06-04 22:08:14 +03:00
|
|
|
#[macro_use]
|
2016-05-29 22:51:08 +03:00
|
|
|
extern crate diesel;
|
|
|
|
extern crate r2d2_diesel;
|
2016-07-22 01:20:38 +03:00
|
|
|
extern crate dotenv;
|
2015-11-20 01:43:04 +03:00
|
|
|
|
2016-05-29 22:51:08 +03:00
|
|
|
use nickel_diesel::{DieselMiddleware, DieselRequestExtensions};
|
|
|
|
use r2d2::NopErrorHandler;
|
2016-05-04 14:16:20 +03:00
|
|
|
use chrono::Duration as ChDuration;
|
2015-12-25 20:32:11 +03:00
|
|
|
use chrono::Datelike;
|
2016-07-22 01:20:38 +03:00
|
|
|
use dotenv::dotenv;
|
2015-11-22 15:01:39 +03:00
|
|
|
use hyper::header::{Expires, HttpDate};
|
2016-09-20 08:32:22 +03:00
|
|
|
use nickel::{FormBody, Halt, HttpRouter, MediaType, MiddlewareResult, Nickel,
|
2016-05-11 19:14:23 +03:00
|
|
|
Request, Response, StaticFilesHandler};
|
2016-05-01 16:50:49 +03:00
|
|
|
use nickel::extensions::response::Redirect;
|
2016-05-04 14:16:20 +03:00
|
|
|
use nickel_jwt_session::{SessionMiddleware, SessionRequestExtensions,
|
|
|
|
SessionResponseExtensions};
|
2015-11-23 08:48:09 +03:00
|
|
|
use time::Duration;
|
2016-04-19 19:10:26 +03:00
|
|
|
use nickel::status::StatusCode;
|
2016-06-04 22:08:14 +03:00
|
|
|
use diesel::expression::sql_literal::SqlLiteral;
|
2016-05-29 22:51:08 +03:00
|
|
|
use diesel::prelude::*;
|
|
|
|
use diesel::pg::PgConnection;
|
2016-06-04 22:08:14 +03:00
|
|
|
use chrono::naive::date::NaiveDate;
|
2015-11-21 18:21:44 +03:00
|
|
|
|
2016-08-09 02:41:42 +03:00
|
|
|
use rphotos::models::{Camera, Person, Photo, Place, Tag};
|
2015-12-25 20:32:11 +03:00
|
|
|
|
2015-11-23 08:48:09 +03:00
|
|
|
mod env;
|
2016-05-01 16:50:49 +03:00
|
|
|
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;
|
|
|
|
|
2015-11-30 22:31:23 +03:00
|
|
|
mod requestloggermiddleware;
|
|
|
|
use requestloggermiddleware::RequestLoggerMiddleware;
|
|
|
|
|
2016-04-19 19:10:26 +03:00
|
|
|
mod photosdirmiddleware;
|
|
|
|
use photosdirmiddleware::{PhotosDirMiddleware, PhotosDirRequestExtensions};
|
2015-11-20 01:43:04 +03:00
|
|
|
|
2016-07-22 01:20:38 +03:00
|
|
|
#[macro_use]
|
|
|
|
mod nickelext;
|
|
|
|
use nickelext::FromSlug;
|
2016-04-22 23:42:10 +03:00
|
|
|
|
2016-09-20 08:32:22 +03:00
|
|
|
use templates::Html;
|
|
|
|
pub static CSSLINK: Html<&'static str> =
|
|
|
|
Html(include!(concat!(env!("OUT_DIR"), "/stylelink")));
|
|
|
|
|
2015-12-06 18:33:25 +03:00
|
|
|
macro_rules! render {
|
|
|
|
($res:expr, $template:expr, { $($param:ident : $ptype:ty = $value:expr),* })
|
|
|
|
=>
|
|
|
|
{
|
|
|
|
{
|
|
|
|
#[derive(Debug, Clone, RustcEncodable)]
|
|
|
|
struct ParamData {
|
2016-04-22 23:42:10 +03:00
|
|
|
csslink: String,
|
2015-12-06 18:33:25 +03:00
|
|
|
$(
|
|
|
|
$param: $ptype,
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
$res.render($template, &ParamData {
|
2016-04-22 23:42:10 +03:00
|
|
|
csslink: include!(concat!(env!("OUT_DIR"), "/stylelink")).into(),
|
2015-12-06 18:33:25 +03:00
|
|
|
$(
|
|
|
|
$param: $value,
|
|
|
|
)*
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-04 22:08:14 +03:00
|
|
|
#[derive(Debug, Clone, RustcEncodable)]
|
2016-09-20 08:32:22 +03:00
|
|
|
pub struct Group {
|
2016-01-31 23:15:44 +03:00
|
|
|
title: String,
|
|
|
|
url: String,
|
|
|
|
count: i64,
|
|
|
|
photo: Photo,
|
|
|
|
}
|
|
|
|
|
2016-07-09 17:04:19 +03:00
|
|
|
#[derive(Debug, Clone, RustcEncodable)]
|
|
|
|
struct Coord {
|
|
|
|
x: f64,
|
|
|
|
y: f64,
|
|
|
|
}
|
|
|
|
|
2016-08-06 00:21:24 +03:00
|
|
|
fn monthname(n: u32) -> &'static str {
|
2016-01-31 23:15:44 +03:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-22 01:20:38 +03:00
|
|
|
|
2015-11-20 01:43:04 +03:00
|
|
|
fn main() {
|
2016-07-22 01:20:38 +03:00
|
|
|
dotenv().ok();
|
2015-11-20 01:43:04 +03:00
|
|
|
env_logger::init().unwrap();
|
2015-11-21 18:21:44 +03:00
|
|
|
info!("Initalized logger");
|
|
|
|
|
2015-11-20 01:43:04 +03:00
|
|
|
let mut server = Nickel::new();
|
2015-11-30 22:31:23 +03:00
|
|
|
server.utilize(RequestLoggerMiddleware);
|
2016-05-01 16:50:49 +03:00
|
|
|
server.utilize(SessionMiddleware::new(&jwt_key()));
|
2016-04-22 23:42:10 +03:00
|
|
|
// 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));
|
2016-06-30 02:29:02 +03:00
|
|
|
let dm: DieselMiddleware<PgConnection> =
|
|
|
|
DieselMiddleware::new(&dburl(), 5, Box::new(NopErrorHandler)).unwrap();
|
2016-05-29 22:51:08 +03:00
|
|
|
server.utilize(dm);
|
2016-04-19 19:10:26 +03:00
|
|
|
server.utilize(PhotosDirMiddleware::new(photos_dir()));
|
|
|
|
|
2016-07-22 01:20:38 +03:00
|
|
|
wrap2!(server.get /login, login);
|
|
|
|
wrap2!(server.post /login, do_login);
|
|
|
|
wrap2!(server.get /logout, logout);
|
|
|
|
server.get("/", all_years);
|
|
|
|
wrap2!(server.get /img/:id/:size, show_image);
|
|
|
|
wrap2!(server.get /tag/, tag_all);
|
|
|
|
wrap2!(server.get /tag/:tag, tag_one);
|
|
|
|
wrap2!(server.get /place/, place_all);
|
|
|
|
wrap2!(server.get /place/:slug, place_one);
|
|
|
|
wrap2!(server.get /person/, person_all);
|
|
|
|
wrap2!(server.get /person/:slug, person_one);
|
|
|
|
wrap2!(server.get /details/:id, photo_details);
|
2016-08-20 14:27:27 +03:00
|
|
|
server.get("/0/", all_null_date);
|
2016-07-22 01:20:38 +03:00
|
|
|
wrap2!(server.get /:year/, months_in_year);
|
|
|
|
wrap2!(server.get /:year/:month/, days_in_month);
|
|
|
|
wrap2!(server.get /:year/:month/:day/, all_for_day);
|
|
|
|
wrap2!(server.get /thisday, on_this_day);
|
2016-04-19 19:10:26 +03:00
|
|
|
|
|
|
|
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>)
|
2016-05-01 16:50:49 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-09-20 08:35:01 +03:00
|
|
|
res.clear_jwt();
|
2016-05-01 16:50:49 +03:00
|
|
|
render!(res, "templates/login.tpl", {})
|
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn do_login<'mw>(req: &mut Request,
|
|
|
|
mut res: Response<'mw>)
|
2016-05-01 16:50:49 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-05-11 19:14:23 +03:00
|
|
|
let form_data = try_with!(res, req.form_body());
|
2016-07-02 13:50:25 +03:00
|
|
|
if let (Some(user), Some(pw)) = (form_data.get("user"),
|
|
|
|
form_data.get("password")) {
|
|
|
|
use rphotos::schema::users::dsl::*;
|
|
|
|
if let Ok(hash) = users.filter(username.eq(user))
|
2016-08-06 00:21:24 +03:00
|
|
|
.select(password)
|
|
|
|
.first::<String>(c) {
|
|
|
|
debug!("Hash for {} is {}", user, hash);
|
|
|
|
if djangohashers::check_password_tolerant(pw, &hash) {
|
|
|
|
info!("User {} logged in", user);
|
|
|
|
res.set_jwt_user(user);
|
|
|
|
return res.redirect("/");
|
2016-07-02 13:50:25 +03:00
|
|
|
}
|
2016-08-06 00:21:24 +03:00
|
|
|
debug!("Password verification failed");
|
|
|
|
} else {
|
|
|
|
debug!("No hash found for {}", user);
|
|
|
|
}
|
2016-05-01 16:50:49 +03:00
|
|
|
}
|
2016-05-11 19:14:23 +03:00
|
|
|
render!(res, "templates/login.tpl", {})
|
2016-05-01 16:50:49 +03:00
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn logout<'mw>(_req: &mut Request,
|
|
|
|
mut res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
2016-09-20 08:35:01 +03:00
|
|
|
res.clear_jwt();
|
2016-05-01 16:50:49 +03:00
|
|
|
res.redirect("/")
|
|
|
|
}
|
|
|
|
|
2016-07-22 01:20:38 +03:00
|
|
|
enum SizeTag {
|
|
|
|
Small,
|
|
|
|
Medium,
|
|
|
|
Large,
|
|
|
|
}
|
|
|
|
impl SizeTag {
|
|
|
|
fn px(&self) -> u32 {
|
|
|
|
match *self {
|
|
|
|
SizeTag::Small => 200,
|
|
|
|
SizeTag::Medium => 800,
|
|
|
|
SizeTag::Large => 1200,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromSlug for SizeTag {
|
2016-08-04 19:16:17 +03:00
|
|
|
fn parse(slug: &str) -> Option<Self> {
|
2016-07-22 01:20:38 +03:00
|
|
|
match slug {
|
|
|
|
"s" => Some(SizeTag::Small),
|
|
|
|
"m" => Some(SizeTag::Medium),
|
|
|
|
"l" => Some(SizeTag::Large),
|
2016-08-06 00:21:24 +03:00
|
|
|
_ => None,
|
2016-07-22 01:20:38 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn show_image<'mw>(req: &Request,
|
|
|
|
mut res: Response<'mw>,
|
|
|
|
the_id: i32,
|
|
|
|
size: SizeTag)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::photos;
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-07-22 01:20:38 +03:00
|
|
|
if let Ok(tphoto) = photos.find(the_id).first::<Photo>(c) {
|
|
|
|
if req.authorized_user().is_some() || tphoto.is_public() {
|
|
|
|
let size = size.px();
|
|
|
|
match req.photos().get_scaled_image(tphoto, size, size) {
|
|
|
|
Ok(buf) => {
|
|
|
|
res.set(MediaType::Jpeg);
|
|
|
|
res.set(Expires(HttpDate(time::now() +
|
|
|
|
Duration::days(14))));
|
|
|
|
return res.send(buf);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
return res.error(StatusCode::InternalServerError,
|
|
|
|
format!("{}", err));
|
2016-05-03 00:52:06 +03:00
|
|
|
}
|
2015-11-28 11:53:12 +03:00
|
|
|
}
|
|
|
|
}
|
2016-04-19 19:10:26 +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> {
|
2016-08-21 14:31:25 +03:00
|
|
|
use rphotos::schema::tags::dsl::{id, tag_name, tags};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-08-08 01:24:15 +03:00
|
|
|
let query = tags.into_boxed();
|
|
|
|
let query = if req.authorized_user().is_some() {
|
|
|
|
query
|
|
|
|
} else {
|
|
|
|
use rphotos::schema::photo_tags::dsl as tp;
|
|
|
|
use rphotos::schema::photos::dsl as p;
|
|
|
|
query.filter(id.eq_any(tp::photo_tags
|
|
|
|
.select(tp::tag_id)
|
|
|
|
.filter(tp::photo_id
|
|
|
|
.eq_any(p::photos
|
|
|
|
.select(p::id)
|
|
|
|
.filter(p::is_public)))))
|
|
|
|
};
|
2016-04-19 19:10:26 +03:00
|
|
|
return render!(res, "templates/tags.tpl", {
|
2016-06-30 02:29:02 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-08-08 01:24:15 +03:00
|
|
|
tags: Vec<Tag> = query
|
|
|
|
.order(tag_name)
|
|
|
|
.load(c)
|
|
|
|
.expect("List tags")
|
2016-04-19 19:10:26 +03:00
|
|
|
});
|
|
|
|
}
|
2016-07-22 01:20:38 +03:00
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn tag_one<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
tslug: String)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::tags::dsl::{slug, tags};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-06-30 02:29:02 +03:00
|
|
|
if let Ok(tag) = tags.filter(slug.eq(tslug)).first::<Tag>(c) {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::id;
|
2016-06-30 02:29:02 +03:00
|
|
|
use rphotos::schema::photo_tags::dsl::{photo_id, photo_tags, tag_id};
|
2016-04-19 19:10:26 +03:00
|
|
|
return render!(res, "templates/tag.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-08-05 19:58:50 +03:00
|
|
|
photos: Vec<Photo> = Photo::query(req.authorized_user().is_some())
|
2016-08-06 00:21:24 +03:00
|
|
|
.filter(id.eq_any(photo_tags.select(photo_id)
|
|
|
|
.filter(tag_id.eq(tag.id))))
|
2016-06-30 02:29:02 +03:00
|
|
|
.load(c).unwrap(),
|
|
|
|
// TODO
|
|
|
|
// .desc_nulls_last("grade")
|
|
|
|
// .desc_nulls_last("date")
|
|
|
|
tag: Tag = tag
|
2016-04-19 19:10:26 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
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> {
|
2016-08-08 01:24:15 +03:00
|
|
|
use rphotos::schema::places::dsl::{id, place_name, places};
|
|
|
|
let query = places.into_boxed();
|
|
|
|
let query = if req.authorized_user().is_some() {
|
|
|
|
query
|
|
|
|
} else {
|
|
|
|
use rphotos::schema::photo_places::dsl as pp;
|
|
|
|
use rphotos::schema::photos::dsl as p;
|
|
|
|
query.filter(id.eq_any(pp::photo_places
|
|
|
|
.select(pp::place_id)
|
|
|
|
.filter(pp::photo_id
|
|
|
|
.eq_any(p::photos
|
|
|
|
.select(p::id)
|
|
|
|
.filter(p::is_public)))))
|
|
|
|
};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-04-19 19:10:26 +03:00
|
|
|
return render!(res, "templates/places.tpl", {
|
2016-06-30 14:16:12 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-08-08 01:24:15 +03:00
|
|
|
places: Vec<Place> = query
|
|
|
|
.order(place_name).load(c).expect("List places")
|
2016-04-19 19:10:26 +03:00
|
|
|
});
|
|
|
|
}
|
2016-06-30 14:16:12 +03:00
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn place_one<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
tslug: String)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::places::dsl::{places, slug};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-06-30 14:16:12 +03:00
|
|
|
if let Ok(place) = places.filter(slug.eq(tslug)).first::<Place>(c) {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::id;
|
2016-08-06 00:21:24 +03:00
|
|
|
use rphotos::schema::photo_places::dsl::{photo_id, photo_places,
|
|
|
|
place_id};
|
2016-04-19 19:10:26 +03:00
|
|
|
return render!(res, "templates/place.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-08-05 19:58:50 +03:00
|
|
|
photos: Vec<Photo> = Photo::query(req.authorized_user().is_some())
|
2016-08-06 00:21:24 +03:00
|
|
|
.filter(id.eq_any(photo_places.select(photo_id)
|
|
|
|
.filter(place_id.eq(place.id))))
|
2016-06-30 14:16:12 +03:00
|
|
|
.load(c).unwrap(),
|
|
|
|
// TODO
|
|
|
|
// .desc_nulls_last("grade")
|
|
|
|
// .desc_nulls_last("date")
|
|
|
|
place: Place = place
|
2016-04-19 19:10:26 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
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> {
|
2016-08-08 01:24:15 +03:00
|
|
|
use rphotos::schema::people::dsl::{id, people, person_name};
|
|
|
|
let query = people.into_boxed();
|
|
|
|
let query = if req.authorized_user().is_some() {
|
|
|
|
query
|
|
|
|
} else {
|
|
|
|
use rphotos::schema::photo_people::dsl as pp;
|
|
|
|
use rphotos::schema::photos::dsl as p;
|
|
|
|
query.filter(id.eq_any(pp::photo_people
|
|
|
|
.select(pp::person_id)
|
|
|
|
.filter(pp::photo_id
|
|
|
|
.eq_any(p::photos
|
|
|
|
.select(p::id)
|
|
|
|
.filter(p::is_public)))))
|
|
|
|
};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-04-19 19:10:26 +03:00
|
|
|
return render!(res, "templates/people.tpl", {
|
2016-06-30 13:15:07 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-08-08 01:24:15 +03:00
|
|
|
people: Vec<Person> = query
|
|
|
|
.order(person_name).load(c).expect("list people")
|
2016-04-19 19:10:26 +03:00
|
|
|
});
|
|
|
|
}
|
2016-06-30 13:15:07 +03:00
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn person_one<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
tslug: String)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::people::dsl::{people, slug};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-06-30 13:15:07 +03:00
|
|
|
if let Ok(person) = people.filter(slug.eq(tslug)).first::<Person>(c) {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::id;
|
2016-08-06 00:21:24 +03:00
|
|
|
use rphotos::schema::photo_people::dsl::{person_id, photo_id,
|
|
|
|
photo_people};
|
2016-04-19 19:10:26 +03:00
|
|
|
return render!(res, "templates/person.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-08-05 19:58:50 +03:00
|
|
|
photos: Vec<Photo> = Photo::query(req.authorized_user().is_some())
|
2016-06-30 13:15:07 +03:00
|
|
|
.filter(id.eq_any(photo_people.select(photo_id)
|
|
|
|
.filter(person_id.eq(person.id))))
|
|
|
|
.load(c).unwrap(),
|
|
|
|
// TODO
|
|
|
|
// .desc_nulls_last("grade")
|
|
|
|
// .desc_nulls_last("date")
|
|
|
|
person: Person = person
|
2016-04-19 19:10:26 +03:00
|
|
|
});
|
|
|
|
}
|
2016-06-30 13:15:07 +03:00
|
|
|
res.error(StatusCode::NotFound, "Not a person")
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn photo_details<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
id: i32)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-07-22 01:20:38 +03:00
|
|
|
use rphotos::schema::photos::dsl::photos;
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-07-22 01:20:38 +03:00
|
|
|
if let Ok(tphoto) = photos.find(id).first::<Photo>(c) {
|
|
|
|
if req.authorized_user().is_some() || tphoto.is_public() {
|
|
|
|
return render!(res, "templates/details.tpl", {
|
|
|
|
user: Option<String> = req.authorized_user(),
|
|
|
|
lpath: Vec<Link> =
|
|
|
|
tphoto.date
|
|
|
|
.map(|d| vec![Link::year(d.year()),
|
2016-08-06 00:21:24 +03:00
|
|
|
Link::month(d.year(), d.month()),
|
|
|
|
Link::day(d.year(), d.month(), d.day())])
|
2016-07-22 01:20:38 +03:00
|
|
|
.unwrap_or_else(|| vec![]),
|
|
|
|
people: Vec<Person> = {
|
|
|
|
use rphotos::schema::people::dsl::{people, id};
|
|
|
|
use rphotos::schema::photo_people::dsl::{photo_people, photo_id, person_id};
|
|
|
|
people.filter(id.eq_any(photo_people.select(person_id)
|
|
|
|
.filter(photo_id.eq(tphoto.id))))
|
|
|
|
.load(c).unwrap()
|
|
|
|
},
|
|
|
|
places: Vec<Place> = {
|
|
|
|
use rphotos::schema::places::dsl::{places, id};
|
|
|
|
use rphotos::schema::photo_places::dsl::{photo_places, photo_id, place_id};
|
|
|
|
places.filter(id.eq_any(photo_places.select(place_id)
|
|
|
|
.filter(photo_id.eq(tphoto.id))))
|
|
|
|
.load(c).unwrap()
|
|
|
|
},
|
|
|
|
tags: Vec<Tag> = {
|
|
|
|
use rphotos::schema::tags::dsl::{tags, id};
|
|
|
|
use rphotos::schema::photo_tags::dsl::{photo_tags, photo_id, tag_id};
|
|
|
|
tags.filter(id.eq_any(photo_tags.select(tag_id)
|
|
|
|
.filter(photo_id.eq(tphoto.id))))
|
|
|
|
.load(c).unwrap()
|
|
|
|
},
|
|
|
|
position: Option<Coord> = {
|
|
|
|
use rphotos::schema::positions::dsl::*;
|
|
|
|
match positions.filter(photo_id.eq(tphoto.id))
|
|
|
|
.select((latitude, longitude))
|
|
|
|
.first::<(i32, i32)>(c) {
|
|
|
|
Ok((tlat, tlong)) => Some(Coord {
|
|
|
|
x: tlat as f64 / 1e6,
|
|
|
|
y: tlong as f64 / 1e6,
|
|
|
|
}),
|
|
|
|
Err(diesel::NotFound) => None,
|
|
|
|
Err(err) => {
|
|
|
|
error!("Failed to read position: {}", err);
|
|
|
|
None
|
2016-07-09 17:04:19 +03:00
|
|
|
}
|
2016-07-22 01:20:38 +03:00
|
|
|
}
|
|
|
|
},
|
2016-08-09 02:41:42 +03:00
|
|
|
camera: Option<Camera> = {
|
|
|
|
use rphotos::schema::cameras::dsl::*;
|
|
|
|
tphoto.camera_id.map(|i| {
|
|
|
|
cameras.find(i).first(c).unwrap()
|
|
|
|
})
|
|
|
|
},
|
2016-05-29 22:51:08 +03:00
|
|
|
time: String = match tphoto.date {
|
2016-04-19 19:10:26 +03:00
|
|
|
Some(d) => d.format("%T").to_string(),
|
|
|
|
None => "".to_string()
|
|
|
|
},
|
2016-05-29 22:51:08 +03:00
|
|
|
year: Option<i32> = tphoto.date.map(|d| d.year()),
|
|
|
|
month: Option<u32> = tphoto.date.map(|d| d.month()),
|
2016-06-04 22:08:14 +03:00
|
|
|
day: Option<u32> = tphoto.date.map(|d| d.day()),
|
2016-07-22 01:20:38 +03:00
|
|
|
photo: Photo = tphoto
|
|
|
|
});
|
2015-11-28 11:53:12 +03:00
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
2016-06-04 22:08:14 +03:00
|
|
|
res.error(StatusCode::NotFound, "Photo not found")
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn all_years<'mw>(req: &mut Request,
|
|
|
|
res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
2016-06-04 22:08:14 +03:00
|
|
|
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::{date, grade};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-06-04 22:08:14 +03:00
|
|
|
|
2016-09-20 08:32:22 +03:00
|
|
|
let user: Option<String> = req.authorized_user();
|
|
|
|
let groups: Vec<Group> =
|
2016-08-21 14:31:25 +03:00
|
|
|
SqlLiteral::new(format!(
|
|
|
|
"select cast(extract(year from date) as int) y, count(*) c \
|
|
|
|
from photos{} group by y order by y",
|
|
|
|
if req.authorized_user().is_none() {
|
|
|
|
" where is_public"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}))
|
|
|
|
.load::<(Option<i32>, i64)>(c).unwrap()
|
2016-06-04 22:08:14 +03:00
|
|
|
.iter().map(|&(year, count)| {
|
2016-08-05 19:58:50 +03:00
|
|
|
let q = Photo::query(req.authorized_user().is_some())
|
2016-06-10 16:36:24 +03:00
|
|
|
.order((grade.desc(), date.asc()))
|
2016-06-05 16:55:23 +03:00
|
|
|
.limit(1);
|
|
|
|
let photo =
|
|
|
|
if let Some(year) = year {
|
|
|
|
q.filter(date.ge(NaiveDate::from_ymd(year, 1, 1)
|
|
|
|
.and_hms(0, 0, 0)))
|
|
|
|
.filter(date.lt(NaiveDate::from_ymd(year + 1, 1, 1)
|
|
|
|
.and_hms(0, 0, 0)))
|
|
|
|
.first::<Photo>(c).unwrap()
|
|
|
|
} else {
|
|
|
|
q.filter(date.is_null())
|
|
|
|
.first::<Photo>(c).unwrap()
|
|
|
|
};
|
|
|
|
Group {
|
|
|
|
title: year.map(|y|format!("{}", y))
|
|
|
|
.unwrap_or("-".to_string()),
|
2016-08-21 14:31:25 +03:00
|
|
|
url: format!("/{}/", year.unwrap_or(0)),
|
2016-06-05 16:55:23 +03:00
|
|
|
count: count,
|
|
|
|
photo: photo
|
|
|
|
}
|
2016-09-20 08:32:22 +03:00
|
|
|
}).collect();
|
|
|
|
|
|
|
|
let mut stream = try!(res.start());
|
2016-09-23 17:40:54 +03:00
|
|
|
match templates::groups(&mut stream, "All photos", Vec::new(), user, groups) {
|
2016-09-20 08:32:22 +03:00
|
|
|
Ok(()) => Ok(Halt(stream)),
|
|
|
|
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
|
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn months_in_year<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
year: i32)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::{date, grade};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-08-06 00:21:24 +03:00
|
|
|
|
2016-09-20 08:32:22 +03:00
|
|
|
let user: Option<String> = req.authorized_user();
|
|
|
|
let title: String = format!("Photos from {}", year);
|
|
|
|
let groups: Vec<Group> =
|
2016-08-21 14:31:25 +03:00
|
|
|
SqlLiteral::new(format!(
|
|
|
|
"select cast(extract(month from date) as int) m, count(*) c \
|
|
|
|
from photos where extract(year from date)={}{} \
|
|
|
|
group by m order by m",
|
|
|
|
year,
|
|
|
|
if req.authorized_user().is_none() {
|
|
|
|
" and is_public"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}))
|
|
|
|
.load::<(Option<i32>, i64)>(c).unwrap()
|
2016-07-22 01:20:38 +03:00
|
|
|
.iter().map(|&(month, count)| {
|
|
|
|
let month = month.map(|y| y as u32).unwrap_or(0);
|
|
|
|
let fromdate = NaiveDate::from_ymd(year, month, 1).and_hms(0, 0, 0);
|
|
|
|
let todate =
|
|
|
|
if month == 12 { NaiveDate::from_ymd(year + 1, 1, 1) }
|
|
|
|
else { NaiveDate::from_ymd(year, month + 1, 1) }
|
|
|
|
.and_hms(0, 0, 0);
|
2016-08-05 19:58:50 +03:00
|
|
|
let photo = Photo::query(req.authorized_user().is_some())
|
2016-07-22 01:20:38 +03:00
|
|
|
.filter(date.ge(fromdate))
|
|
|
|
.filter(date.lt(todate))
|
|
|
|
.order((grade.desc(), date.asc()))
|
|
|
|
.limit(1)
|
|
|
|
.first::<Photo>(c).unwrap();
|
|
|
|
|
|
|
|
Group {
|
2016-08-06 00:21:24 +03:00
|
|
|
title: monthname(month).to_string(),
|
2016-07-22 01:20:38 +03:00
|
|
|
url: format!("/{}/{}/", year, month),
|
|
|
|
count: count,
|
|
|
|
photo: photo
|
|
|
|
}
|
2016-09-20 08:32:22 +03:00
|
|
|
}).collect();
|
|
|
|
|
|
|
|
let mut stream = try!(res.start());
|
2016-09-23 17:40:54 +03:00
|
|
|
match templates::groups(&mut stream, &title, Vec::new(), user, groups) {
|
2016-09-20 08:32:22 +03:00
|
|
|
Ok(()) => Ok(Halt(stream)),
|
|
|
|
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
|
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn days_in_month<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
year: i32,
|
2016-08-06 00:21:24 +03:00
|
|
|
month: u32)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::{date, grade};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-06-04 22:08:14 +03:00
|
|
|
|
2016-09-23 17:40:54 +03:00
|
|
|
let user: Option<String> = req.authorized_user();
|
|
|
|
let lpath: Vec<Link> = vec![Link::year(year)];
|
|
|
|
let title: String = format!("Photos from {} {}", monthname(month), year);
|
|
|
|
let groups: Vec<Group> =
|
2016-08-21 14:31:25 +03:00
|
|
|
SqlLiteral::new(format!(
|
|
|
|
"select cast(extract(day from date) as int) d, count(*) c \
|
|
|
|
from photos where extract(year from date)={} \
|
|
|
|
and extract(month from date)={}{} group by d order by d",
|
|
|
|
year, month,
|
|
|
|
if req.authorized_user().is_none() {
|
|
|
|
" and is_public"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}))
|
|
|
|
.load::<(Option<i32>, i64)>(c).unwrap()
|
2016-07-22 01:20:38 +03:00
|
|
|
.iter().map(|&(day, count)| {
|
|
|
|
let day = day.map(|y| y as u32).unwrap_or(0);
|
2016-08-06 00:21:24 +03:00
|
|
|
let fromdate = NaiveDate::from_ymd(year, month, day)
|
2016-07-22 01:20:38 +03:00
|
|
|
.and_hms(0, 0, 0);
|
2016-08-05 19:58:50 +03:00
|
|
|
let photo = Photo::query(req.authorized_user().is_some())
|
2016-07-22 01:20:38 +03:00
|
|
|
.filter(date.ge(fromdate))
|
|
|
|
.filter(date.lt(fromdate + ChDuration::days(1)))
|
|
|
|
.order((grade.desc(), date.asc()))
|
|
|
|
.limit(1)
|
|
|
|
.first::<Photo>(c).unwrap();
|
|
|
|
|
|
|
|
Group {
|
|
|
|
title: format!("{}", day),
|
|
|
|
url: format!("/{}/{}/{}", year, month, day),
|
|
|
|
count: count,
|
|
|
|
photo: photo
|
|
|
|
}
|
2016-09-23 17:40:54 +03:00
|
|
|
}).collect();
|
|
|
|
|
|
|
|
let mut stream = try!(res.start());
|
|
|
|
match templates::groups(&mut stream, &title, lpath, user, groups) {
|
|
|
|
Ok(()) => Ok(Halt(stream)),
|
|
|
|
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
|
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
|
2016-08-20 14:27:27 +03:00
|
|
|
fn all_null_date<'mw>(req: &mut Request,
|
|
|
|
res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
use rphotos::schema::photos::dsl::{date, path};
|
|
|
|
|
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-09-23 17:40:54 +03:00
|
|
|
let mut stream = try!(res.start());
|
|
|
|
match templates::index(&mut stream,
|
|
|
|
&"Photos without a date",
|
|
|
|
vec![],
|
|
|
|
req.authorized_user(),
|
|
|
|
Photo::query(req.authorized_user().is_some())
|
|
|
|
.filter(date.is_null())
|
|
|
|
.order(path.asc())
|
|
|
|
.limit(500)
|
|
|
|
.load(c).unwrap()) {
|
|
|
|
Ok(()) => Ok(Halt(stream)),
|
|
|
|
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
|
|
|
|
}
|
2016-08-20 14:27:27 +03:00
|
|
|
}
|
|
|
|
|
2016-05-04 14:16:20 +03:00
|
|
|
fn all_for_day<'mw>(req: &mut Request,
|
2016-07-22 01:20:38 +03:00
|
|
|
res: Response<'mw>,
|
|
|
|
year: i32,
|
2016-08-06 00:21:24 +03:00
|
|
|
month: u32,
|
2016-07-22 01:20:38 +03:00
|
|
|
day: u32)
|
2016-05-04 14:16:20 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-06 00:21:24 +03:00
|
|
|
let thedate = NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0);
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::{date, grade};
|
2016-07-22 01:20:38 +03:00
|
|
|
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-09-23 17:40:54 +03:00
|
|
|
|
|
|
|
let user: Option<String> = req.authorized_user();
|
|
|
|
let lpath: Vec<Link> = vec![Link::year(year),
|
|
|
|
Link::month(year, month)];
|
|
|
|
let title: String = format!("Photos from {} {} {}",
|
|
|
|
day, monthname(month), year);
|
|
|
|
let photos: Vec<Photo> = Photo::query(req.authorized_user().is_some())
|
2016-08-07 21:38:42 +03:00
|
|
|
.filter(date.ge(thedate))
|
|
|
|
.filter(date.lt(thedate + ChDuration::days(1)))
|
|
|
|
.order((grade.desc(), date.asc()))
|
|
|
|
.limit(500)
|
2016-09-23 17:40:54 +03:00
|
|
|
.load(c).unwrap();
|
|
|
|
|
|
|
|
let mut stream = try!(res.start());
|
|
|
|
match templates::index(&mut stream, &title, lpath, user, photos) {
|
|
|
|
Ok(()) => Ok(Halt(stream)),
|
|
|
|
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
|
|
|
|
}
|
2015-11-20 01:43:04 +03:00
|
|
|
}
|
2016-05-04 19:45:50 +03:00
|
|
|
|
2016-06-30 17:39:01 +03:00
|
|
|
fn on_this_day<'mw>(req: &mut Request,
|
|
|
|
res: Response<'mw>)
|
2016-08-06 00:21:24 +03:00
|
|
|
-> MiddlewareResult<'mw> {
|
2016-08-07 21:38:42 +03:00
|
|
|
use rphotos::schema::photos::dsl::{date, grade};
|
2016-08-05 19:11:25 +03:00
|
|
|
let c: &PgConnection = &req.db_conn();
|
2016-06-30 17:39:01 +03:00
|
|
|
|
|
|
|
let (month, day) = {
|
|
|
|
let now = time::now();
|
2016-08-06 00:21:24 +03:00
|
|
|
(now.tm_mon as u32 + 1, now.tm_mday as u32)
|
2016-06-30 17:39:01 +03:00
|
|
|
};
|
2016-09-23 17:40:54 +03:00
|
|
|
let mut stream = try!(res.start());
|
|
|
|
match templates::groups(&mut stream,
|
|
|
|
&format!("Photos from {} {}", day, monthname(month)),
|
|
|
|
vec![],
|
|
|
|
req.authorized_user(),
|
|
|
|
SqlLiteral::new(format!(
|
2016-08-21 14:31:25 +03:00
|
|
|
"select extract(year from date) y, count(*) c \
|
|
|
|
from photos where extract(month from date)={} \
|
|
|
|
and extract(day from date)={}{} group by y order by y desc",
|
|
|
|
month, day,
|
|
|
|
if req.authorized_user().is_none() {
|
|
|
|
" and is_public"
|
|
|
|
} else {
|
|
|
|
""
|
|
|
|
}))
|
2016-06-30 17:39:01 +03:00
|
|
|
.load::<(Option<f64>, i64)>(c).unwrap()
|
|
|
|
.iter().map(|&(year, count)| {
|
|
|
|
let year = year.map(|y| y as i32).unwrap_or(0);
|
2016-08-06 00:21:24 +03:00
|
|
|
let fromdate = NaiveDate::from_ymd(year, month as u32, day)
|
|
|
|
.and_hms(0, 0, 0);
|
2016-08-05 19:58:50 +03:00
|
|
|
let photo = Photo::query(req.authorized_user().is_some())
|
2016-06-30 17:39:01 +03:00
|
|
|
.filter(date.ge(fromdate))
|
|
|
|
.filter(date.lt(fromdate + ChDuration::days(1)))
|
|
|
|
.order((grade.desc(), date.asc()))
|
|
|
|
.limit(1)
|
|
|
|
.first::<Photo>(c).unwrap();
|
|
|
|
|
|
|
|
Group {
|
|
|
|
title: format!("{}", year),
|
|
|
|
url: format!("/{}/{}/{}", year, month, day),
|
|
|
|
count: count,
|
|
|
|
photo: photo
|
|
|
|
}
|
|
|
|
}).collect()
|
2016-09-23 17:40:54 +03:00
|
|
|
) {
|
|
|
|
Ok(()) => Ok(Halt(stream)),
|
|
|
|
Err(e) => stream.bail(format!("Problem rendering template: {:?}", e))
|
|
|
|
}
|
2016-06-30 17:39:01 +03:00
|
|
|
}
|
|
|
|
|
2016-05-18 19:44:50 +03:00
|
|
|
#[derive(Debug, Clone, RustcEncodable)]
|
2016-09-20 08:32:22 +03:00
|
|
|
pub struct Link {
|
2016-05-18 19:44:50 +03:00
|
|
|
pub url: String,
|
|
|
|
pub name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Link {
|
|
|
|
fn year(year: i32) -> Self {
|
|
|
|
Link {
|
|
|
|
url: format!("/{}/", year),
|
|
|
|
name: format!("{}", year),
|
|
|
|
}
|
|
|
|
}
|
2016-08-06 00:21:24 +03:00
|
|
|
fn month(year: i32, month: u32) -> Self {
|
2016-05-18 19:44:50 +03:00
|
|
|
Link {
|
|
|
|
url: format!("/{}/{}/", year, month),
|
|
|
|
name: format!("{}", month),
|
|
|
|
}
|
|
|
|
}
|
2016-08-06 00:21:24 +03:00
|
|
|
fn day(year: i32, month: u32, day: u32) -> Self {
|
2016-05-18 19:44:50 +03:00
|
|
|
Link {
|
|
|
|
url: format!("/{}/{}/{}", year, month, day),
|
|
|
|
name: format!("{}", day),
|
|
|
|
}
|
|
|
|
}
|
2016-05-04 19:45:50 +03:00
|
|
|
}
|
2016-09-20 08:32:22 +03:00
|
|
|
|
|
|
|
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|