BLOG-78 Backend image upload and download #84

Merged
squid merged 6 commits from BLOG-78_image_management into main 2025-07-27 13:10:47 +08:00
Owner

Description

  • Add some endpoints about image:
    • POST /image/upload
    • GET /image/{id}

Note

Since there isn't identity authentication, the /image endpoints should be restricted to private network in nginx.

Note

Volume for backend should be configured in pod.yaml.

Package Changes

actix-multipart = "0.7.2"

Screenshots

No response

Reference

Resolves #78

Checklist

  • A milestone is set
  • The related issuse has been linked to this branch
### Description - Add some endpoints about image: - POST `/image/upload` - GET `/image/{id}` > [!NOTE] > Since there isn't identity authentication, the `/image` endpoints should be restricted to private network in nginx. > [!NOTE] > Volume for backend should be configured in `pod.yaml`. ### Package Changes ```toml actix-multipart = "0.7.2" ``` ### Screenshots _No response_ ### Reference Resolves #78 ### Checklist - [x] A milestone is set - [x] The related issuse has been linked to this branch
squid added this to the 0.3 milestone 2025-07-27 12:26:50 +08:00
squid added 4 commits 2025-07-27 12:26:51 +08:00
BLOG-78 refactor: rename db mapper
All checks were successful
Frontend CI / build (push) Successful in 1m5s
7b431e3fc8
BLOG-78 feat: implement image upload and retrieval feature
All checks were successful
Frontend CI / build (push) Successful in 1m9s
d58a7d1a37
This commit introduces the capability to upload and serve images.

A new `image` feature module has been added to the backend, following the existing clean architecture pattern.

- Implements `POST /image/upload` for uploading image files.
- Implements `GET /image/{id}` for retrieving an image by its ID.
- Adds a new `image` table to the database to store image metadata.
- Image data is stored in the file system. The path can be configured via the `STORAGE_PATH` environment variable.
BLOG-78 feat: add MIME type validation for image uploads
All checks were successful
Frontend CI / build (push) Successful in 1m8s
c639a85ff3
BLOG-78 feat: add volume and storage path for static files in Dockerfile
All checks were successful
Frontend CI / build (push) Successful in 1m5s
PR Title Check / pr-title-check (pull_request) Successful in 13s
Auto Comment On PR / add_improve_comment (pull_request) Successful in 13s
5a30642b8f
Collaborator

/improve

/improve
Collaborator

PR Code Suggestions

CategorySuggestion                                                                                                                                    Impact
General
Avoid unnecessary data cloning

The to_entity method currently clones the data vector. If ImageRequestDto is a
transient object used solely to create an Image entity, it can take ownership of the
data to avoid an unnecessary deep copy, which can be inefficient for large images.
Change the method to consume self.

backend/feature/image/src/adapter/delivery/image_request_dto.rs [9-14]

-pub fn to_entity(&self) -> Image {
+pub fn to_entity(self) -> Image {
     Image {
         id: None,
-        mime_type: self.mime_type.clone(),
-        data: self.data.clone(),
+        mime_type: self.mime_type,
+        data: self.data,
     }
 }
Suggestion importance[1-10]: 9

__

Why: This is a significant optimization. By consuming self in to_entity, the method avoids unnecessary deep cloning of the data vector, which can be large, leading to better performance and more idiomatic Rust.

High
Construct complete objects directly

The current implementation constructs an Image entity in two steps: first, an
incomplete Image with empty data from ImageDbMapper::to_entity, then mutating it
with data from storage. It's more robust and idiomatic to construct the Image object
fully in one step, combining metadata from image_mapper and data from image_storage.
This also allows removing the problematic to_entity method from ImageDbMapper.

backend/feature/image/src/adapter/gateway/image_repository_impl.rs [44-47]

 let image_mapper = self.image_db_service.get_image_info_by_id(id).await?;
-let mut image = image_mapper.to_entity();
-image.data = self.image_storage.read_data(id)?;
-Ok(image)
+let data = self.image_storage.read_data(id)?;
+Ok(Image {
+    id: image_mapper.id,
+    mime_type: image_mapper.mime_type,
+    data,
+})
Suggestion importance[1-10]: 8

__

Why: This suggestion improves code clarity and robustness by constructing the Image object in a single step, avoiding an intermediate, incomplete state and an unnecessary Vec::new() allocation. It's a good idiomatic Rust practice.

Medium
## PR Code Suggestions ✨ <!-- --> <table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=2>General</td> <td> <details><summary>Avoid unnecessary data cloning</summary> ___ **The <code>to_entity</code> method currently clones the <code>data</code> vector. If <code>ImageRequestDto</code> is a <br>transient object used solely to create an <code>Image</code> entity, it can take ownership of the <br><code>data</code> to avoid an unnecessary deep copy, which can be inefficient for large images. <br>Change the method to consume <code>self</code>.** [backend/feature/image/src/adapter/delivery/image_request_dto.rs [9-14]](https://git.squidspirit.com/squid/blog/src/branch/BLOG-78_image_management/backend/feature/image/src/adapter/delivery/image_request_dto.rs#L9-L14) ```diff -pub fn to_entity(&self) -> Image { +pub fn to_entity(self) -> Image { Image { id: None, - mime_type: self.mime_type.clone(), - data: self.data.clone(), + mime_type: self.mime_type, + data: self.data, } } ``` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: This is a significant optimization. By consuming `self` in `to_entity`, the method avoids unnecessary deep cloning of the `data` vector, which can be large, leading to better performance and more idiomatic Rust. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Construct complete objects directly</summary> ___ **The current implementation constructs an <code>Image</code> entity in two steps: first, an <br>incomplete <code>Image</code> with empty data from <code>ImageDbMapper::to_entity</code>, then mutating it <br>with data from storage. It's more robust and idiomatic to construct the <code>Image</code> object <br>fully in one step, combining metadata from <code>image_mapper</code> and data from <code>image_storage</code>. <br>This also allows removing the problematic <code>to_entity</code> method from <code>ImageDbMapper</code>.** [backend/feature/image/src/adapter/gateway/image_repository_impl.rs [44-47]](https://git.squidspirit.com/squid/blog/src/branch/BLOG-78_image_management/backend/feature/image/src/adapter/gateway/image_repository_impl.rs#L44-L47) ```diff let image_mapper = self.image_db_service.get_image_info_by_id(id).await?; -let mut image = image_mapper.to_entity(); -image.data = self.image_storage.read_data(id)?; -Ok(image) +let data = self.image_storage.read_data(id)?; +Ok(Image { + id: image_mapper.id, + mime_type: image_mapper.mime_type, + data, +}) ``` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: This suggestion improves code clarity and robustness by constructing the `Image` object in a single step, avoiding an intermediate, incomplete state and an unnecessary `Vec::new()` allocation. It's a good idiomatic Rust practice. </details></details></td><td align=center>Medium </td></tr></tr></tbody></table>
squid added 1 commit 2025-07-27 12:52:55 +08:00
BLOG-78 refactor: simplify get_image_by_id implementation in ImageRepositoryImpl
All checks were successful
PR Title Check / pr-title-check (pull_request) Successful in 13s
Frontend CI / build (push) Successful in 1m8s
aaf43f21bd
Author
Owner

Construct complete objects directly

The current implementation constructs an Image entity in two steps: first, an
incomplete Image with empty data from ImageDbMapper::to_entity, then mutating it
with data from storage. It's more robust and idiomatic to construct the Image object
fully in one step, combining metadata from image_mapper and data from image_storage.
This also allows removing the problematic to_entity method from ImageDbMapper.

backend/feature/image/src/adapter/gateway/image_repository_impl.rs [44-47]

 let image_mapper = self.image_db_service.get_image_info_by_id(id).await?;
-let mut image = image_mapper.to_entity();
-image.data = self.image_storage.read_data(id)?;
-Ok(image)
+let data = self.image_storage.read_data(id)?;
+Ok(Image {
+    id: image_mapper.id,
+    mime_type: image_mapper.mime_type,
+    data,
+})

Addressed in aaf43f21bd

> Construct complete objects directly > > The current implementation constructs an <code>Image</code> entity in two steps: first, an <br>incomplete <code>Image</code> with empty data from <code>ImageDbMapper::to_entity</code>, then mutating it <br>with data from storage. It's more robust and idiomatic to construct the <code>Image</code> object <br>fully in one step, combining metadata from <code>image_mapper</code> and data from <code>image_storage</code>. <br>This also allows removing the problematic <code>to_entity</code> method from <code>ImageDbMapper</code>. > > [backend/feature/image/src/adapter/gateway/image_repository_impl.rs [44-47]](https://git.squidspirit.com/squid/blog/src/branch/BLOG-78_image_management/backend/feature/image/src/adapter/gateway/image_repository_impl.rs#L44-L47) > > ```diff > let image_mapper = self.image_db_service.get_image_info_by_id(id).await?; > -let mut image = image_mapper.to_entity(); > -image.data = self.image_storage.read_data(id)?; > -Ok(image) > +let data = self.image_storage.read_data(id)?; > +Ok(Image { > + id: image_mapper.id, > + mime_type: image_mapper.mime_type, > + data, > +}) > ``` Addressed in aaf43f21bd12f4ca0dcea67288625d95fb56a189
squid added 1 commit 2025-07-27 13:04:28 +08:00
BLOG-78 refactor: rename to_entity methods to into_entity for consistency
All checks were successful
PR Title Check / pr-title-check (pull_request) Successful in 16s
Frontend CI / build (push) Successful in 1m5s
f5c12ce56d
Author
Owner

Avoid unnecessary data cloning

The to_entity method currently clones the data vector. If ImageRequestDto is a
transient object used solely to create an Image entity, it can take ownership of the
data to avoid an unnecessary deep copy, which can be inefficient for large images.
Change the method to consume self.

backend/feature/image/src/adapter/delivery/image_request_dto.rs [9-14]

-pub fn to_entity(&self) -> Image {
+pub fn to_entity(self) -> Image {
     Image {
         id: None,
-        mime_type: self.mime_type.clone(),
-        data: self.data.clone(),
+        mime_type: self.mime_type,
+        data: self.data,
     }
 }

Addressed in f5c12ce56d

> Avoid unnecessary data cloning > > > The <code>to_entity</code> method currently clones the <code>data</code> vector. If <code>ImageRequestDto</code> is a <br>transient object used solely to create an <code>Image</code> entity, it can take ownership of the <br><code>data</code> to avoid an unnecessary deep copy, which can be inefficient for large images. <br>Change the method to consume <code>self</code>. > > [backend/feature/image/src/adapter/delivery/image_request_dto.rs [9-14]](https://git.squidspirit.com/squid/blog/src/branch/BLOG-78_image_management/backend/feature/image/src/adapter/delivery/image_request_dto.rs#L9-L14) > > ```diff > -pub fn to_entity(&self) -> Image { > +pub fn to_entity(self) -> Image { > Image { > id: None, > - mime_type: self.mime_type.clone(), > - data: self.data.clone(), > + mime_type: self.mime_type, > + data: self.data, > } > } > ``` Addressed in f5c12ce56d6f9390e544869ffb2f0b68d817637d
squid merged commit ab3050db69 into main 2025-07-27 13:10:47 +08:00
squid deleted branch BLOG-78_image_management 2025-07-27 13:10:47 +08:00
Sign in to join this conversation.
No description provided.