Add simple api for image data.
This commit is contained in:
parent
9c3deff82d
commit
772f17ac92
@ -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
145
src/server/api.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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(())
|
||||
|
Loading…
Reference in New Issue
Block a user