Skip to content

Commit

Permalink
feat: extend image controller with get all and delete endpoints
Browse files Browse the repository at this point in the history
* fix access to blog create/update

* allow specifying folder for image upload
  • Loading branch information
PetoMPP committed Dec 6, 2023
1 parent 34d7026 commit fabc316
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Rocket.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
form = "64 kB"
json = "1 MiB"
msgpack = "2 MiB"
file = "5 MiB"
file = "20 MiB"

[default]
address = "0.0.0.0"
Expand Down
3 changes: 2 additions & 1 deletion src/controllers/blog.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::controller::Controller;
use crate::services::azure_blob::AzureBlobService;
use crate::{auth::claims::AdminClaims, services::azure_blob::AzureBlobService};
use petompp_web_models::{
error::{ApiError, Error, ValidationError},
models::{
Expand All @@ -24,6 +24,7 @@ impl Controller for BlogController {

#[post("/<name>/<lang>", data = "<value>")]
async fn create_or_update<'a>(
_claims: AdminClaims,
name: &'a str,
lang: &'a str,
blob_service: &'a State<AzureBlobService>,
Expand Down
44 changes: 38 additions & 6 deletions src/controllers/image.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use super::controller::Controller;
use crate::{auth::claims::Claims, services::azure_blob::AzureBlobService};
use crate::{
auth::claims::{AdminClaims, Claims},
services::azure_blob::AzureBlobService,
};
use petompp_web_models::{
error::{ApiError, Error},
models::api_response::ApiResponse,
};
use rocket::{
data::{Limits, ToByteUnit},
delete, get,
http::{ContentType, Status},
put, routes,
serde::json::Json,
Expand All @@ -20,12 +24,21 @@ impl Controller for ImageController {
}

fn routes(&self) -> Vec<rocket::Route> {
routes![upload]
routes![upload, get_all, delete]
}
}

#[put("/", data = "<img>")]
#[get("/")]
async fn get_all(
blob_service: &State<AzureBlobService>,
) -> Result<Json<ApiResponse<Vec<String>>>, ApiError> {
Ok(Json(ApiResponse::ok(blob_service.get_image_paths().await?)))
}

#[put("/?<folder>&<filename>", data = "<img>")]
async fn upload<'a>(
folder: &'a str,
filename: Option<&'a str>,
_claims: Claims,
content_type: &ContentType,
limits: &Limits,
Expand All @@ -38,17 +51,36 @@ async fn upload<'a>(
let Some(ext) = content_type.extension() else {
return Err(Error::from(Status::BadRequest).into());
};
let filename = format!("{}.{}", uuid::Uuid::new_v4(), ext);
let filename = match filename {
Some(filename) => format!("{}.{}", filename, ext),
None => format!("{}.{}", uuid::Uuid::new_v4(), ext),
};
let data = img
.open(limits.get("file").unwrap_or(5.mebibytes()))
.open(limits.get("file").unwrap_or(20.mebibytes()))
.into_bytes()
.await
.map_err(|_| Error::from(Status::InternalServerError))?;
if !data.is_complete() {
return Err(Error::from(Status::PayloadTooLarge).into());
}
blob_service
.upload_img(filename.clone(), data.to_vec(), content_type.to_string())
.upload_img(
filename.clone(),
folder.to_string(),
data.to_vec(),
content_type.to_string(),
)
.await?;
Ok(Json(ApiResponse::ok(filename)))
}

#[delete("/?<pattern>")]
async fn delete<'a>(
pattern: &'a str,
_claims: AdminClaims,
blob_service: &State<AzureBlobService>,
) -> Result<Json<ApiResponse<'a, usize>>, ApiError<'a>> {
Ok(Json(ApiResponse::ok(
blob_service.delete_img(pattern.to_string()).await?,
)))
}
47 changes: 45 additions & 2 deletions src/services/azure_blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,69 @@ impl AzureBlobService {
}

const IMAGE_CONTAINER: &str = "image-upload";
pub async fn get_image_paths(&self) -> Result<Vec<String>, Error> {
let mut stream = self
.client
.clone()
.blob_service_client()
.container_client(Self::IMAGE_CONTAINER.to_string())
.list_blobs()
.into_stream();
let mut result = Vec::new();
while let Some(resp) = stream.next().await {
for blob in resp?.blobs.blobs().cloned() {
if blob.properties.content_type.starts_with("image/") {
result.push(blob.name);
}
}
}
Ok(result)
}

pub async fn upload_img(
&self,
name: String,
folder: String,
data: Vec<u8>,
content_type: String,
) -> Result<(), Error> {
const IMAGE_FOLDER: &str = "editor";
Ok(self
.client
.clone()
.blob_client(
Self::IMAGE_CONTAINER.to_string(),
format!("{}/{}", IMAGE_FOLDER, name),
format!("{}/{}", folder, name),
)
.put_block_blob(data)
.content_type(content_type)
.await
.map(|_| ())?)
}

pub async fn delete_img(&self, pattern: String) -> Result<usize, Error> {
Ok(self
.client
.clone()
.blob_service_client()
.container_client(Self::IMAGE_CONTAINER.to_string())
.list_blobs()
.prefix(pattern)
.into_stream()
.fold(Result::<_, Error>::Ok(0), |acc, resp| async move {
let mut count = acc?;
for blob in resp?.blobs.blobs().cloned() {
self.client
.clone()
.blob_client(Self::IMAGE_CONTAINER.to_string(), blob.name)
.delete()
.await?;
count += 1;
}
Ok(count)
})
.await?)
}

const BLOG_CONTAINER: &str = "blog";
pub async fn get_blog_meta(&self, id: &String, lang: &Country) -> Result<BlogMetaData, Error> {
let mut stream = self
Expand Down

0 comments on commit fabc316

Please sign in to comment.