From faf44abe9633fab4fedff2481409796fdc4f0fd4 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sun, 17 Nov 2024 16:53:24 +1100 Subject: [PATCH] Please update collection list page to update the style of collection and make sure each collection has the same width and height regardless the image size --- data/db.sqlite3 | Bin 262144 -> 262144 bytes links/api_views.py | 81 ++++++++++-- links/templates/links/collection_list.html | 2 +- links/templates/links/help.html | 139 +++++++++++++++++++++ 4 files changed, 213 insertions(+), 9 deletions(-) diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 5e07870cf66bc06aac8ce642e63878b34ef06234..b0e36e9c2100f165d3ff07f0da6d05994382c368 100644 GIT binary patch delta 247 zcmZo@5NK!+m>|ulG*QNxQK>PZHGy$!0@D(CK33je4E*!?ZTar-Rr3DYY^d;px4xE@ z%~##h(b34lA~o4OG0D`-*wDn(&@3q_$tcMr$uu?9+#t=+)X29|!8t!CCpEbwGe6H! zAsNnA$WK$q%uP&BEjBVRGSM|O)HO6$Ffg?;v9vNY*RwD(H#IcIC1YZ-y;6^(^)&Kwi diff --git a/links/api_views.py b/links/api_views.py index 72d198d..9a36f8c 100644 --- a/links/api_views.py +++ b/links/api_views.py @@ -1,29 +1,51 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response +from django.shortcuts import get_object_or_404 from .models import ImageCollection, Image from .serializers import ImageCollectionSerializer, ImageSerializer from .storage import R2Storage import uuid import logging -import os + logger = logging.getLogger(__name__) class ImageCollectionViewSet(viewsets.ModelViewSet): queryset = ImageCollection.objects.all() serializer_class = ImageCollectionSerializer + def create(self, request, *args, **kwargs): + """Create a new collection""" + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return Response( + { + 'status': 'success', + 'message': 'Collection created successfully', + 'data': serializer.data + }, + status=status.HTTP_201_CREATED, + headers=headers + ) + @action(detail=True, methods=['post']) def upload_images(self, request, pk=None): - """API endpoint to upload images to a collection""" + """Upload images to a collection""" collection = self.get_object() files = request.FILES.getlist('file') storage = R2Storage() + logger.debug(f"Processing upload request for collection {collection.id}") + logger.debug(f"Number of files: {len(files)}") + uploaded_images = [] for file in files: try: - logger.info(os.environ.get('R2_ENDPOINT_URL')) + logger.debug(f"Processing file: {file.name}") + logger.debug(f"File size: {file.size}") + logger.debug(f"Content type: {file.content_type}") # Generate unique file key file_key = f"images/{collection.id}/{uuid.uuid4()}/{file.name}" @@ -51,25 +73,68 @@ class ImageCollectionViewSet(viewsets.ModelViewSet): size=file.size ) + logger.debug(f"Created image record: {image.id}") uploaded_images.append(ImageSerializer(image).data) except Exception as e: logger.error(f"Upload failed for {file.name}", exc_info=True) return Response({ - 'error': str(e) + 'status': 'error', + 'message': str(e) }, status=status.HTTP_400_BAD_REQUEST) - return Response(uploaded_images, status=status.HTTP_201_CREATED) + return Response({ + 'status': 'success', + 'message': f'Successfully uploaded {len(uploaded_images)} images', + 'data': uploaded_images + }, status=status.HTTP_201_CREATED) + + @action(detail=True, methods=['delete']) + def delete_image(self, request, pk=None): + """Delete an image from a collection""" + collection = self.get_object() + image_id = request.data.get('image_id') + + try: + image = collection.images.get(id=image_id) + storage = R2Storage() + + # Delete from storage + if image.file_key: + try: + storage.delete_file(image.file_key) + except Exception as e: + logger.error(f"Failed to delete from storage: {e}") + + # Delete from database + image.delete() + + return Response({ + 'status': 'success', + 'message': 'Image deleted successfully' + }) + + except Image.DoesNotExist: + return Response({ + 'status': 'error', + 'message': 'Image not found' + }, status=status.HTTP_404_NOT_FOUND) + except Exception as e: + return Response({ + 'status': 'error', + 'message': str(e) + }, status=status.HTTP_400_BAD_REQUEST) class ImageViewSet(viewsets.ModelViewSet): queryset = Image.objects.all() serializer_class = ImageSerializer def perform_destroy(self, instance): - # Delete from R2 before deleting record + """Delete image from storage when deleting record""" storage = R2Storage() try: - storage.delete_file(instance.file_key) + if instance.file_key: + storage.delete_file(instance.file_key) except Exception as e: - pass # Continue with deletion even if R2 delete fails + logger.error(f"Failed to delete from storage: {e}") instance.delete() diff --git a/links/templates/links/collection_list.html b/links/templates/links/collection_list.html index e2f7fe6..93a0c94 100644 --- a/links/templates/links/collection_list.html +++ b/links/templates/links/collection_list.html @@ -33,7 +33,7 @@ class="object-cover w-full h-full"> {% empty %} -
+
diff --git a/links/templates/links/help.html b/links/templates/links/help.html index d3b621d..6aeb174 100644 --- a/links/templates/links/help.html +++ b/links/templates/links/help.html @@ -213,6 +213,145 @@ curl -X POST http://localhost:8000/api/newsletters \ }' ``` +## Collection API Usage + +GoLinks provides RESTful APIs for managing image collections. Here are the available endpoints and their usage: + +### List Collections + +Retrieve a paginated list of all collections: +``` +GET /api/collections +GET /api/collections?page=2 +GET /api/collections?page_size=20 +``` +Response example: +```json +{ + "count": 10, + "next": "http://localhost:8000/api/collections?page=2", + "previous": null, + "results": [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "My Collection", + "description": "A collection of images", + "image_count": 5, + "created_at": "2024-01-01T00:00:00Z" + }, + // ... more collections + ] +} +``` + +### Create a Collection + +Create a new image collection: +``` +POST /api/collections +Content-Type: application/json + +{ + "name": "My Collection", + "description": "A collection of images" +} +``` +Response example: +```json +{ + "status": "success", + "message": "Collection created successfully", + "data": { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "My Collection", + "description": "A collection of images", + "image_count": 0, + "created_at": "2024-01-01T00:00:00Z" + } +} +``` + +### Upload Images to Collection + +Upload one or multiple images to a collection: +``` +POST /api/collections/{collection_id}/upload_images +Content-Type: multipart/form-data + +file: image1.jpg +file: image2.jpg +``` +Response example: +```json +{ + "status": "success", + "message": "Successfully uploaded 2 images", + "data": [ + { + "id": "550e8400-e29b-41d4-a716-446655440001", + "title": "image1.jpg", + "content_type": "image/jpeg", + "size": 1024000, + "url": "https://your-r2-domain.com/images/...", + "created_at": "2024-01-01T00:00:00Z" + }, + { + "id": "550e8400-e29b-41d4-a716-446655440002", + "title": "image2.jpg", + "content_type": "image/jpeg", + "size": 2048000, + "url": "https://your-r2-domain.com/images/...", + "created_at": "2024-01-01T00:00:00Z" + } + ] +} +``` + +### Delete an Image + +Delete a specific image from a collection: +``` +DELETE /api/images/{image_id} +``` +Response example: +```json +{ + "status": "success", + "message": "Image deleted successfully" +} +``` + +### Delete a Collection + +Delete a collection and all its images: +``` +DELETE /api/collections/{collection_id} +``` +Response example: +```json +{ + "status": "success", + "message": "Collection deleted successfully" +} +``` + +### Error Responses + +In case of errors, the API will return appropriate status codes and error messages: +```json +{ + "status": "error", + "message": "Error description here" +} +``` + +Common status codes: +- 200: Success +- 201: Created +- 400: Bad Request +- 404: Not Found +- 500: Internal Server Error + {% endfilter %}