Skip to content

Commit

Permalink
Implement image upload to object storage and fix minio connection #29
Browse files Browse the repository at this point in the history
  • Loading branch information
joelvdavies committed Oct 4, 2024
1 parent 5749384 commit d86cdb8
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 9 deletions.
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
- minio
environment:
DATABASE__HOST_AND_OPTIONS: object_storage_api_mongodb_container:27017/?authMechanism=SCRAM-SHA-256&authSource=admin
extra_hosts:
- "minio:host-gateway"

mongo-db:
image: mongo:7.0-jammy
Expand Down
2 changes: 1 addition & 1 deletion object_storage_api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DATABASE__USERNAME=root
DATABASE__PASSWORD=example
DATABASE__HOST_AND_OPTIONS=localhost:27017/?authMechanism=SCRAM-SHA-256&authSource=admin
DATABASE__NAME=object-storage
OBJECT_STORAGE__ENDPOINT_URL=http://localhost:9000
OBJECT_STORAGE__ENDPOINT_URL=http://minio:9000
OBJECT_STORAGE__ACCESS_KEY=root
OBJECT_STORAGE__SECRET_ACCESS_KEY=example_password
OBJECT_STORAGE__BUCKET_NAME=object-storage
Expand Down
7 changes: 5 additions & 2 deletions object_storage_api/routers/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,24 @@
status_code=status.HTTP_201_CREATED,
)
# pylint:disable=too-many-arguments
# pylint:disable=too-many-positional-arguments
def create_image(
image_service: ImageServiceDep,
# Unfortunately using Annotated[ImagePostSchema, Form()] as on
# https://fastapi.tiangolo.com/tutorial/request-form-models/does not work correctly when there is an UploadFile
# within it, so have to redefine here before passing them to the schema
entity_id: Annotated[str, Form(description="ID of the entity the image relates to")],
file_name: Annotated[str, Form(description="File name of the image")],
file: Annotated[UploadFile, File(description="Image file")],
upload_file: Annotated[UploadFile, File(description="Image file")],
title: Annotated[Optional[str], Form(description="Title of the image")] = None,
description: Annotated[Optional[str], Form(description="Description of the image")] = None,
) -> ImageSchema:
# pylint: disable=missing-function-docstring
logger.info("Creating a new image")

image = ImagePostSchema(entity_id=entity_id, file_name=file_name, file=file, title=title, description=description)
image = ImagePostSchema(
entity_id=entity_id, file_name=file_name, upload_file=upload_file, title=title, description=description
)

logger.debug("Image data: %s", image)

Expand Down
2 changes: 1 addition & 1 deletion object_storage_api/schemas/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ImagePostSchema(ImageSchemaBase):
Schema model for an image creation request.
"""

file: UploadFile = Field(description="Image file")
upload_file: UploadFile = Field(description="Image file")


class ImageSchema(CreatedModifiedSchemaMixin, ImageSchemaBase):
Expand Down
1 change: 1 addition & 0 deletions object_storage_api/services/attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def create(self, attachment: AttachmentPostSchema) -> AttachmentPostResponseSche
:param attachment: Attachment to be created.
:return: Created attachment with an pre-signed upload URL.
:raises InvalidObjectIdError: If the attachment has any invalid ID's in it.
"""

# Generate a unique ID for the attachment - this needs to be known now to avoid inserting into the database
Expand Down
4 changes: 2 additions & 2 deletions object_storage_api/services/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ def create(self, image: ImagePostSchema) -> ImageSchema:
:param image: Image to be created.
:return: Created image with an pre-signed upload URL.
:raises InvalidObjectIdError: If the image has any invalid ID's in it.
"""

# Generate a unique ID for the image - this needs to be known now to avoid inserting into the database
# before generating the presigned URL which would then require transactions
image_id = str(ObjectId())

# TODO: Populate
object_key = "SOME_OBJECT_KEY"
object_key = self._image_store.upload_image(image_id, image)

try:
image_in = ImageIn(**image.model_dump(), id=image_id, object_key=object_key)
Expand Down
5 changes: 2 additions & 3 deletions object_storage_api/stores/attachment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ def create_presigned_post(
self, attachment_id: str, attachment: AttachmentPostSchema
) -> tuple[str, AttachmentPostUploadInfoSchema]:
"""
Creates a presigned post URL for uploading an attachment file.
Creates a presigned post URL for uploading an attachment file to object storage.
:param attachment_id: ID of the attachment to generate the URL for.
:param attachment: Attachment to generate the URL for.
:return: Tuple with
- Object key of the new attachment.
- Object key of the attachment.
- Upload info schema containing a presigned url to upload the attachment file to and the required form
fields for the request.
:raises InvalidObjectIdError: If the attachment has any invalid ID's in it.
"""
object_key = f"attachments/{attachment.entity_id}/{attachment_id}"

Expand Down
20 changes: 20 additions & 0 deletions object_storage_api/stores/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@

import logging

from object_storage_api.core.object_store import object_storage_config, s3_client
from object_storage_api.schemas.image import ImagePostSchema

logger = logging.getLogger()


class ImageStore:
"""
Store for managing images in an S3 object store.
"""

def upload_image(self, image_id: str, image: ImagePostSchema) -> str:
"""
Uploads a given image to object storage.
:param attachment_id: ID of the attachment to generate the URL for.
:param attachment: Attachment to generate the URL for.
:return: Object key of the image.
"""
object_key = f"images/{image.entity_id}/{image_id}"

logger.info("Uploading image file to the object storage")
s3_client.upload_fileobj(
image.upload_file.file, Bucket=object_storage_config.bucket_name.get_secret_value(), Key=object_key
)

return object_key

0 comments on commit d86cdb8

Please sign in to comment.