Add simple api for image data.

This commit is contained in:
Rasmus Kaj 2019-10-29 23:35:57 +01:00
parent 9c3deff82d
commit 772f17ac92
3 changed files with 154 additions and 0 deletions

View File

@ -385,4 +385,11 @@ impl SizeTag {
SizeTag::Large => 1900,
}
}
pub fn tag(self) -> char {
match self {
SizeTag::Small => 's',
SizeTag::Medium => 'm',
SizeTag::Large => 'l',
}
}
}

145
src/server/api.rs Normal file
View File

@ -0,0 +1,145 @@
//! API views
use super::Context;
use crate::models::{Photo, SizeTag};
use diesel::{self, prelude::*, result::Error as DbError};
use log::warn;
use serde::{Deserialize, Serialize};
use warp::filters::BoxedFilter;
use warp::http::StatusCode;
use warp::reply::Response;
use warp::{Filter, Reply};
pub fn routes(s: BoxedFilter<(Context,)>) -> BoxedFilter<(impl Reply,)> {
use warp::filters::method::v2::get;
use warp::{path, query};
let img = path("image").and(s.clone()).and(query()).map(get_img);
get().and(img).boxed()
}
#[derive(Debug, Deserialize)]
struct ImgQuery {
id: Option<u32>,
path: Option<String>,
}
impl ImgQuery {
fn validate(self) -> Result<ImgIdentifier, &'static str> {
match (self.id, self.path) {
(None, None) => Err("id or path required"),
(Some(id), None) => Ok(ImgIdentifier::Id(id)),
(None, Some(path)) => Ok(ImgIdentifier::Path(path)),
(Some(_), Some(_)) => Err("Conflicting arguments"),
}
}
}
enum ImgIdentifier {
Id(u32),
Path(String),
}
impl ImgIdentifier {
fn load(&self, db: &PgConnection) -> Result<Option<Photo>, DbError> {
use crate::schema::photos::dsl as p;
match &self {
ImgIdentifier::Id(ref id) => {
p::photos.filter(p::id.eq(*id as i32)).first(db)
}
ImgIdentifier::Path(path) => {
p::photos.filter(p::path.eq(path)).first(db)
}
}
.optional()
}
}
fn get_img(context: Context, q: ImgQuery) -> Response {
q.validate()
.map_err(ApiError::bad_request)
.and_then(|id| {
let db = context.db()?;
let img = id.load(&db)?.ok_or(NOT_FOUND)?;
if !context.is_authorized() && !img.is_public() {
return Err(NOT_FOUND);
}
Ok(warp::reply::json(&GetImgResult {
small: ImgLink::new(&img, SizeTag::Small),
medium: ImgLink::new(&img, SizeTag::Medium),
public: img.is_public,
})
.into_response())
})
.unwrap_or_else(|err| err.into_response())
}
struct ApiError {
code: StatusCode,
msg: &'static str,
}
const NOT_FOUND: ApiError = ApiError::bad_request("not found");
impl ApiError {
const fn bad_request(msg: &'static str) -> Self {
ApiError {
code: StatusCode::BAD_REQUEST,
msg,
}
}
fn into_response(self) -> Response {
let mut response =
warp::reply::json(&ApiErrorMessage { err: self.msg })
.into_response();
*response.status_mut() = self.code;
response
}
}
impl From<diesel::result::Error> for ApiError {
fn from(err: diesel::result::Error) -> ApiError {
warn!("Diesel error in api: {}", err);
ApiError {
code: StatusCode::INTERNAL_SERVER_ERROR,
msg: "database error",
}
}
}
impl From<r2d2_memcache::r2d2::Error> for ApiError {
fn from(err: r2d2_memcache::r2d2::Error) -> ApiError {
warn!("R2D2 error in api: {}", err);
ApiError {
code: StatusCode::INTERNAL_SERVER_ERROR,
msg: "pool error",
}
}
}
#[derive(Debug, Serialize)]
struct ApiErrorMessage {
err: &'static str,
}
#[derive(Debug, Serialize)]
struct GetImgResult {
small: ImgLink,
medium: ImgLink,
public: bool,
}
#[derive(Debug, Serialize)]
struct ImgLink {
url: String,
width: u32,
height: u32,
}
impl ImgLink {
fn new(img: &Photo, size: SizeTag) -> Self {
let (width, height) = img.get_size(size);
ImgLink {
url: format!("/img/{}-{}.jpg", img.id, size.tag()),
width,
height,
}
}
}

View File

@ -1,4 +1,5 @@
mod admin;
mod api;
mod context;
mod image;
mod login;
@ -104,6 +105,7 @@ pub fn run(args: &Args) -> Result<(), Error> {
.or(get().and(path("ac")).and(path("tag")).and(s()).and(query()).map(auto_complete_tag))
.or(get().and(path("ac")).and(path("person")).and(s()).and(query()).map(auto_complete_person))
.or(get().and(path("search")).and(end()).and(s()).and(query()).map(search))
.or(path("api").and(api::routes(s())))
.or(path("adm").and(admin::routes(s())));
warp::serve(routes.recover(customize_error)).run(args.listen);
Ok(())