Skip to content

Commit

Permalink
Implement server side
Browse files Browse the repository at this point in the history
  • Loading branch information
TobiasDeBruijn committed Oct 19, 2023
1 parent 1f7fe01 commit af6c848
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 11 deletions.
33 changes: 30 additions & 3 deletions server/chroma/src/routes/v1/photo/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use crate::routes::v1::PhotoQuality;
use actix_multiresponse::Payload;
use actix_web::web;
use dal::database::Photo;
use dal::storage_engine::StorageEngineError;
use dal::DalError;
use image::{DynamicImage, ImageOutputFormat};
use proto::GetPhotoResponse;
use proto::get_photo_response::Response;
use proto::{GetPhotoResponse, PhotoResponseType};
use serde::Deserialize;
use std::io::Cursor;
use tap::TapFallible;
Expand All @@ -27,7 +29,7 @@ pub struct Query {
format: ImageFormat,
}

#[derive(Debug, Default, Deserialize)]
#[derive(Eq, PartialEq, Debug, Default, Deserialize)]
pub enum ImageFormat {
#[default]
Png,
Expand All @@ -50,6 +52,28 @@ pub async fn get(
.await?
.ok_or(Error::NotFound)?;

if query.format.eq(&ImageFormat::WebP) {
match photo
.photo_to_url(&data.storage, query.quality_preference.clone().into())
.await
{
Ok(p) => {
return Ok(Payload(GetPhotoResponse {
response_type: PhotoResponseType::Url as i32,
response: Some(Response::Url(p)),
}));
}
Err(e) => match e {
DalError::Storage(e) => match e {
// URL mode is not supported
StorageEngineError::NotSupported => {}
_ => return Err(e.into()),
},
DalError::Db(e) => return Err(e.into()),
},
}
}

let mut proto = photo
.photo_to_proto(&data.storage, query.quality_preference.clone().into())
.await
Expand All @@ -60,7 +84,10 @@ pub async fn get(

proto.photo_data = convert_format(proto.photo_data, &query.format)?;

Ok(Payload(GetPhotoResponse { photo: Some(proto) }))
Ok(Payload(GetPhotoResponse {
response_type: PhotoResponseType::InResponse as i32,
response: Some(Response::Photo(proto)),
}))
}

fn convert_format(bytes: Vec<u8>, format: &ImageFormat) -> WebResult<Vec<u8>> {
Expand Down
16 changes: 15 additions & 1 deletion server/dal/src/database/photo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,26 @@ impl<'a> Photo<'a> {
pub const ID_PREFIX: &'static str = "PH_";
pub const MAX_ID_LEN: usize = 32;

pub async fn photo_to_url(
&self,
storage: &StorageEngine,
quality_preference: PhotoQuality,
) -> Result<String, DalError> {
let has_pref = self.is_quality_created(quality_preference.clone()).await?;
let quality = if has_pref {
quality_preference
} else {
PhotoQuality::Original
};

Ok(storage.get_photo_by_id_as_url(&self.id, quality).await?)
}

/// Convert a [Photo] to a [proto::Photo].
/// Retrieves the photo's content from S3.
///
/// # Errors
///
pub async fn photo_to_proto(
self,
storage: &StorageEngine,
Expand Down
24 changes: 18 additions & 6 deletions server/dal/src/s3.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use async_recursion::async_recursion;
use aws_credential_types::Credentials;
use aws_sdk_s3::error::{DeleteObjectError, GetObjectError, PutObjectError};
use aws_sdk_s3::presigning::config::PresigningConfig;
use aws_sdk_s3::types::{ByteStream, SdkError};
use aws_sdk_s3::{Client, Config, Region};
use std::ops::Deref;
use std::time::Duration;
use thiserror::Error;

pub mod aws_errors {
Expand Down Expand Up @@ -40,6 +41,8 @@ pub enum S3Error {
DeleteObject(#[from] SdkError<DeleteObjectError>),
#[error("Failed to convert ByteStream: {0}")]
ByteStream(#[from] aws_smithy_http::byte_stream::error::Error),
#[error("Failed to create presigning config: {0}")]
Presigning(#[from] aws_sdk_s3::presigning::config::Error),
}

pub struct S3Config {
Expand Down Expand Up @@ -72,11 +75,20 @@ impl S3 {
})
}

#[async_recursion]
pub async fn get_photo_by_id<S: AsRef<str> + Send + Sync>(
&self,
photo_id: S,
) -> Result<Vec<u8>, S3Error> {
pub async fn get_signed_url_by_id<S: AsRef<str>>(&self, id: S) -> Result<String, S3Error> {
let response = self
.client
.get_object()
.bucket(&self.bucket_name)
.key(id.as_ref())
.presigned(PresigningConfig::expires_in(Duration::from_secs(6000))?)
.await?;

let url = response.uri();
Ok(url.to_string())
}

pub async fn get_photo_by_id<S: AsRef<str>>(&self, photo_id: S) -> Result<Vec<u8>, S3Error> {
let photo = self
.get_object()
.bucket(&self.bucket_name)
Expand Down
15 changes: 15 additions & 0 deletions server/dal/src/storage_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use tracing::debug;

#[derive(Debug, Error)]
pub enum StorageEngineError {
#[error("Operation not supported")]
NotSupported,
/// S3 Error
#[error("{0}")]
S3(#[from] S3Error),
Expand Down Expand Up @@ -56,6 +58,19 @@ impl StorageEngine {
Ok(Self(StorageEngineMode::File(FileEngine { base_path })))
}

pub async fn get_photo_by_id_as_url<S: AsRef<str>>(
&self,
photo_id: S,
quality_preference: PhotoQuality,
) -> Result<String, StorageEngineError> {
let quality_id = fmt_id_with_quality(photo_id.as_ref(), &quality_preference);

match &self.0 {
StorageEngineMode::File(_) => Err(StorageEngineError::NotSupported),
StorageEngineMode::S3(client) => Ok(client.get_signed_url_by_id(&quality_id).await?),
}
}

/// Retrieve a photo by ID.
///
/// # Errors
Expand Down
11 changes: 10 additions & 1 deletion server/proto/protos/payload/v1/photo/get.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,14 @@ package nl.svsticky.chroma;
import "entity/photo.proto";

message GetPhotoResponse {
Photo photo = 1;
PhotoResponseType responseType = 1;
oneof response {
string url = 2;
Photo photo = 3;
}
}

enum PhotoResponseType {
URL = 0;
InResponse = 1;
}

0 comments on commit af6c848

Please sign in to comment.