2015-11-20 01:43:04 +03:00
|
|
|
#[macro_use] extern crate nickel;
|
2015-11-21 18:21:44 +03:00
|
|
|
#[macro_use] extern crate log;
|
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 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-11-20 01:43:04 +03:00
|
|
|
|
2015-12-25 20:32:11 +03:00
|
|
|
use chrono::UTC;
|
|
|
|
use chrono::offset::TimeZone;
|
|
|
|
use chrono::{Duration as ChDuration};
|
|
|
|
use chrono::Datelike;
|
2015-11-22 15:01:39 +03:00
|
|
|
use hyper::header::{Expires, HttpDate};
|
2016-04-19 19:10:26 +03:00
|
|
|
use nickel::{MediaType, HttpRouter, Nickel, StaticFilesHandler, Request, Response, MiddlewareResult};
|
2016-05-01 16:50:49 +03:00
|
|
|
use nickel::extensions::response::Redirect;
|
|
|
|
use nickel_jwt_session::{SessionMiddleware, SessionRequestExtensions, SessionResponseExtensions};
|
2015-11-27 15:05:43 +03:00
|
|
|
use plugin::{Pluggable};
|
2015-11-23 08:48:09 +03:00
|
|
|
use rustc_serialize::Encodable;
|
2016-01-31 23:15:44 +03:00
|
|
|
use rustorm::query::{Query, Filter};
|
2015-11-23 08:48:09 +03:00
|
|
|
use time::Duration;
|
2016-04-19 19:10:26 +03:00
|
|
|
use nickel::status::StatusCode;
|
2016-05-01 16:50:49 +03:00
|
|
|
use std::io::Read;
|
2015-11-21 18:21:44 +03:00
|
|
|
|
2015-12-25 20:32:11 +03:00
|
|
|
mod models;
|
|
|
|
use models::{Entity, Photo, Tag, Person, Place, query_for};
|
|
|
|
|
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-27 15:05:43 +03:00
|
|
|
mod rustormmiddleware;
|
|
|
|
use rustormmiddleware::{RustormMiddleware, RustormRequestExtensions};
|
2015-11-23 08:48:09 +03:00
|
|
|
|
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-04-22 23:42:10 +03:00
|
|
|
|
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,
|
|
|
|
)*
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-22 20:22:21 +03:00
|
|
|
fn orm_get_related<T: Entity, Src: Entity>(src: &Src, rel_table: &str)
|
|
|
|
-> Query
|
|
|
|
{
|
|
|
|
let mut q = Query::select();
|
|
|
|
q.only_from(&T::table());
|
|
|
|
q.left_join_table(rel_table, &format!("{}.id", T::table().name),
|
|
|
|
&format!("{}.{}", rel_table, T::table().name))
|
|
|
|
.filter_eq(&format!("{}.{}", rel_table, Src::table().name), src.id());
|
|
|
|
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",
|
|
|
|
_ => "non-month"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-20 01:43:04 +03:00
|
|
|
fn main() {
|
|
|
|
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));
|
2015-11-22 15:01:39 +03:00
|
|
|
server.utilize(RustormMiddleware::new(&dburl()));
|
2016-04-19 19:10:26 +03:00
|
|
|
server.utilize(PhotosDirMiddleware::new(photos_dir()));
|
|
|
|
|
2016-05-01 16:50:49 +03:00
|
|
|
server.get("/login", login);
|
|
|
|
server.post("/login", do_login);
|
|
|
|
server.get("/logout", logout);
|
2016-04-19 19:10:26 +03:00
|
|
|
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-01 16:50:49 +03:00
|
|
|
fn login<'mw>(_req: &mut Request, mut res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
res.clear_jwt_user();
|
|
|
|
render!(res, "templates/login.tpl", {})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn do_login<'mw>(req: &mut Request, mut res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
// TODO It seems form data parsing is next version of nickel ...
|
|
|
|
let mut form_data = String::new();
|
|
|
|
req.origin.read_to_string(&mut form_data).unwrap();
|
|
|
|
println!("Form: {:?}", form_data);
|
|
|
|
if form_data == "user=kaj&password=kaj123" {
|
|
|
|
res.set_jwt_user("kaj");
|
|
|
|
res.redirect("/")
|
|
|
|
} else {
|
|
|
|
render!(res, "templates/login.tpl", {})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn logout<'mw>(_req: &mut Request, mut res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
res.clear_jwt_user();
|
|
|
|
res.redirect("/")
|
|
|
|
}
|
|
|
|
|
2016-04-19 19:10:26 +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 let Some(size) = match req.param("size").unwrap() {
|
|
|
|
"s" => Some(200),
|
|
|
|
"m" => Some(800),
|
|
|
|
"l" => Some(1200),
|
|
|
|
_ => None
|
|
|
|
} {
|
|
|
|
let buf = req.photos().get_scaled_image(photo, size, size);
|
|
|
|
res.set(MediaType::Jpeg);
|
|
|
|
res.set(Expires(HttpDate(time::now() + Duration::days(14))));
|
|
|
|
return res.send(buf);
|
2015-11-28 11:53:12 +03:00
|
|
|
}
|
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "No such image")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tag_all<'mw>(req: &mut Request, res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
return render!(res, "templates/tags.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
tags: Vec<Tag> = query_for::<Tag>().asc("tag")
|
|
|
|
.collect(req.db_conn()).unwrap()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
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", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
photos: Vec<Photo> =
|
|
|
|
req.orm_get_related(&tag, "photo_tag").unwrap(),
|
|
|
|
tag: Tag = tag
|
|
|
|
});
|
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a tag")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn place_all<'mw>(req: &mut Request, res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
return render!(res, "templates/places.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
places: Vec<Place> = query_for::<Place>().asc("place")
|
|
|
|
.collect(req.db_conn()).unwrap()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
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", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
photos: Vec<Photo> =
|
|
|
|
req.orm_get_related(&place, "photo_place").unwrap(),
|
|
|
|
place: Place = place
|
|
|
|
});
|
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a place")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn person_all<'mw>(req: &mut Request, res: Response<'mw>)
|
|
|
|
-> MiddlewareResult<'mw> {
|
|
|
|
return render!(res, "templates/people.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
people: Vec<Person> = query_for::<Person>().asc("name")
|
|
|
|
.collect(req.db_conn()).unwrap()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
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", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
photos: Vec<Photo> =
|
|
|
|
orm_get_related::<Photo, Person>(&person, "photo_person")
|
|
|
|
.desc_nulls_last("grade")
|
|
|
|
.desc_nulls_last("date")
|
|
|
|
.collect(req.db_conn()).unwrap(),
|
|
|
|
person: Person = person
|
|
|
|
});
|
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a place")
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
return render!(res, "templates/details.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
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()
|
|
|
|
},
|
|
|
|
year: i32 = match photo.date {
|
|
|
|
Some(d) => d.year(),
|
|
|
|
None => 0
|
|
|
|
},
|
|
|
|
month: u32 = match photo.date {
|
|
|
|
Some(d) => d.month(),
|
|
|
|
None => 0
|
|
|
|
},
|
|
|
|
day: u32 = match photo.date {
|
|
|
|
Some(d) => d.day(),
|
|
|
|
None => 0
|
|
|
|
},
|
|
|
|
photo: Photo = photo
|
2015-12-06 18:33:25 +03:00
|
|
|
});
|
2015-11-28 11:53:12 +03:00
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a year")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn all_years<'mw>(req: &mut Request, res: Response<'mw>) -> MiddlewareResult<'mw> {
|
|
|
|
return render!(res, "templates/groups.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
title: &'static str = "All photos",
|
|
|
|
groups: Vec<Group> = query_for::<Photo>()
|
|
|
|
.columns(vec!("extract(year from date) y", "count(*) c"))
|
|
|
|
.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>()
|
|
|
|
.filter_eq("extract(year from date)", &(year as f64))
|
|
|
|
.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()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
title: String = format!("Photos from {}", year),
|
|
|
|
groups: Vec<Group> = query_for::<Photo>()
|
|
|
|
.columns(vec!("extract(month from date) m", "count(*) c"))
|
|
|
|
.filter_eq("extract(year from date)", &(year as f64))
|
|
|
|
.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>()
|
|
|
|
.filter_eq("extract(year from date)", &(year as f64))
|
|
|
|
.filter_eq("extract(month from date)", &(month as f64))
|
2015-12-22 20:22:21 +03:00
|
|
|
.desc_nulls_last("grade")
|
2016-04-19 19:10:26 +03:00
|
|
|
.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
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}).collect()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a year")
|
|
|
|
}
|
|
|
|
|
|
|
|
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", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
title: String = format!("Photos from {} {}", monthname(month),
|
|
|
|
year),
|
|
|
|
groups: Vec<Group> = query_for::<Photo>()
|
|
|
|
.columns(vec!("extract(day from date) d", "count(*) c"))
|
|
|
|
.filter_eq("extract(year from date)", &(year as f64))
|
|
|
|
.filter_eq("extract(month from date)", &(month as f64))
|
|
|
|
.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 : Photo = query_for::<Photo>()
|
2016-01-31 23:15:44 +03:00
|
|
|
.filter_eq("extract(year from date)", &(year as f64))
|
2016-04-19 19:10:26 +03:00
|
|
|
.filter_eq("extract(month from date)", &(month as f64))
|
|
|
|
.filter_eq("extract(day from date)", &(day as f64))
|
|
|
|
.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
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}).collect()
|
|
|
|
});
|
2015-12-25 20:32:11 +03:00
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a month")
|
|
|
|
}
|
|
|
|
|
|
|
|
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>() {
|
|
|
|
let date = UTC.ymd(year, month as u32, day).and_hms(0,0,0);
|
|
|
|
return render!(res, "templates/index.tpl", {
|
2016-05-01 16:50:49 +03:00
|
|
|
user: Option<String> = req.authorized_user(),
|
2016-04-19 19:10:26 +03:00
|
|
|
title: String = format!("Photos from {} {} {}",
|
|
|
|
day, monthname(month), year),
|
|
|
|
photos: Vec<Photo> = query_for::<Photo>()
|
|
|
|
.filter_gte("date", &date)
|
|
|
|
.filter_lt("date", &(date + ChDuration::days(1)))
|
|
|
|
.desc_nulls_last("grade")
|
|
|
|
.asc_nulls_last("date")
|
|
|
|
.collect(req.db_conn()).unwrap()
|
|
|
|
})
|
2015-12-25 20:32:11 +03:00
|
|
|
}
|
|
|
|
}
|
2016-04-19 19:10:26 +03:00
|
|
|
}
|
|
|
|
res.error(StatusCode::NotFound, "Not a day")
|
2015-11-20 01:43:04 +03:00
|
|
|
}
|