From 004d4e544503fadcb45af368eb1f8082aaa4b9d9 Mon Sep 17 00:00:00 2001 From: ngovinh2k2 Date: Fri, 9 Jan 2026 14:13:25 +0700 Subject: [PATCH] feat: add upload file --- common/apps/oauth2/views.py | 3 +- common/apps/organization/tasks.py | 4 ++- common/apps/upload_file/__init__.py | 0 common/apps/upload_file/service.py | 43 +++++++++++++++++++++++++++++ common/apps/upload_file/urls.py | 12 ++++++++ common/apps/upload_file/views.py | 32 +++++++++++++++++++++ 6 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 common/apps/upload_file/__init__.py create mode 100644 common/apps/upload_file/service.py create mode 100644 common/apps/upload_file/urls.py create mode 100644 common/apps/upload_file/views.py diff --git a/common/apps/oauth2/views.py b/common/apps/oauth2/views.py index ad8a909..557d074 100644 --- a/common/apps/oauth2/views.py +++ b/common/apps/oauth2/views.py @@ -6,6 +6,7 @@ from rest_framework.exceptions import ParseError from rest_framework.permissions import AllowAny from rest_framework.response import Response +from rest_framework.views import APIView from common.apps.oauth2.serializers import OauthLoginSerializer from common.utils.encoder import decode_from_base64 @@ -35,7 +36,7 @@ def post(self, request, *args, **kwargs): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -class GoogleLoginCallbackView(generics.RetrieveAPIView): +class GoogleLoginCallbackView(APIView): def get(self, request): code = request.GET.get("code") state = request.GET.get("state") diff --git a/common/apps/organization/tasks.py b/common/apps/organization/tasks.py index 94e6f79..e44d241 100644 --- a/common/apps/organization/tasks.py +++ b/common/apps/organization/tasks.py @@ -20,7 +20,9 @@ def get_new_organization_handler(): @task(name="spacedf.tasks.new_organization", max_retries=3) @transaction.atomic def create_organization(id, name, slug_name, is_active, owner, created_at, updated_at): - logger.info(f"create_organization: owner_email={owner.get('email')}, org_slug={slug_name}") + logger.info( + f"create_organization: owner_email={owner.get('email')}, org_slug={slug_name}" + ) organization = Organization( schema_name=slug_name, diff --git a/common/apps/upload_file/__init__.py b/common/apps/upload_file/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/common/apps/upload_file/service.py b/common/apps/upload_file/service.py new file mode 100644 index 0000000..32a8e07 --- /dev/null +++ b/common/apps/upload_file/service.py @@ -0,0 +1,43 @@ +import logging +import uuid + +import boto3 + +client = boto3.client("s3") + + +def put_presigned_url(bucket_name, expiration=3600): + """ + return presigned URL and file name + """ + try: + file_name = uuid.uuid4() + presigned_url = client.generate_presigned_url( + ClientMethod="put_object", + Params={"Bucket": bucket_name, "Key": f"uploads/{file_name}.png"}, + ExpiresIn=expiration, + HttpMethod="PUT", + ) + return {"file_name": file_name, "presigned_url": presigned_url} + except Exception as e: + logging.error(f"Error: {e}") + return None + + +def get_presigned_url(bucket_name, link_file, expiration=3600): + """ + Return the URL from name file + """ + try: + url_image = client.generate_presigned_url( + ClientMethod="get_object", + Params={ + "Bucket": bucket_name, + "Key": link_file, + }, + ExpiresIn=expiration, + ) + return url_image + except Exception as e: + logging.error(f"Error generating presigned GET URL: {e}") + return None diff --git a/common/apps/upload_file/urls.py b/common/apps/upload_file/urls.py new file mode 100644 index 0000000..c929d69 --- /dev/null +++ b/common/apps/upload_file/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from common.apps.upload_file.views import GetPresignedURL, PutPresignedURL + +urlpatterns = [ + path("presigned-url", PutPresignedURL.as_view(), name="presigned_url"), + path( + "presigned-url/", + GetPresignedURL.as_view(), + name="get_presigned_url", + ), +] diff --git a/common/apps/upload_file/views.py b/common/apps/upload_file/views.py new file mode 100644 index 0000000..ed41939 --- /dev/null +++ b/common/apps/upload_file/views.py @@ -0,0 +1,32 @@ +from django.conf import settings +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from common.apps.upload_file.service import get_presigned_url, put_presigned_url + + +class PutPresignedURL(APIView): + def get(self, request): + data = put_presigned_url(settings.AWS_S3.get("AWS_STORAGE_BUCKET_NAME")) + if data is not None: + return Response(data, status=status.HTTP_200_OK) + + return Response( + {"error": "Get presigned url fail."}, status=status.HTTP_400_BAD_REQUEST + ) + + +class GetPresignedURL(APIView): + def get(self, request, *args, **kwargs): + filename = self.kwargs.get("filename") + link_file = f"uploads/{filename}.png" + data = get_presigned_url( + settings.AWS_S3.get("AWS_STORAGE_BUCKET_NAME"), link_file + ) + if data is not None: + return Response({"url_image": data}, status=status.HTTP_200_OK) + + return Response( + {"error": "Get presigned url fail."}, status=status.HTTP_400_BAD_REQUEST + )