Skip to content

Example №1

Task Deadline
[CITY-13] Issue creation and retrieval 29/12/2024

Problem

Users cannot report issues in their city, view existing issues within a specific area, or retrieve detailed information about an issue. Additionally, images attached to issues are not stored securely in AWS under the issues backet.


Solution

Implement functionality that allows users to:

  1. Report new issues with details, location, and images stored securely in AWS.
  2. View all issues within a specific radius and filter by category or other criteria.
  3. Retrieve detailed information about a specific issue, including associated comments and metadata.

Acceptance Criteria

  • Users can create a new issue by providing a title, description, category, location, and images.
  • Images are uploaded to AWS and linked to the issue.
  • Users can view issues within a specific radius and apply filters (e.g., category, date).
  • Users can retrieve details of a specific issue, including comments and metadata.
  • AWS is fully integrated for secure and scalable image storage.

Database Changes

Issues Table

Column Name Column Type Nullable Default
id UUID No Generated UUID
title VARCHAR(255) No -
description TEXT Yes -
location JSON (or GEOGRAPHY(Point, 4326)) No -
status ENUM No active
category_id FOREIGN KEY(Categories.id) No -
citizen_id FOREIGN KEY(Citizen.id) No -
created_at DATETIME No auto_now_add=True
updated_at DATETIME No auto_now=True

Issue Images Table

Column Name Column Type Nullable Default
issue_id FOREIGN KEY(Issues.id) No -
photo TEXT (URL to AWS) No -

Integration with AWS

All images related to an issue will be uploaded to AWS S3. The URLs of these images will be stored in the issue_image table for retrieval.


Models

from django.contrib.gis.db import models

class IssueStatusChoices(Enum):
    ACTIVE = "active"
    IN_PROGRESS = "in progress"
    RESOLVED = "resolved"
    CANCELLED = "cancelled"

    @classmethod
    def choices(cls):
        return [(choice.name, choice.value) for choice in cls]

class Issue(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    location = models.JSONField()  # Optionally use GEOGRAPHY(Point, 4326)
    status = models.CharField(max_length=20, choices=IssueStatusChoices.choices(), default=IssueStatusChoices.ACTIVE.name)
    category = models.ForeignKey('Categories', on_delete=models.PROTECT)
    citizen = models.ForeignKey('Citizen', on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

class IssueImage(models.Model):
    issue = models.ForeignKey('Issue', on_delete=models.CASCADE)
    photo = models.TextField()  # URL to AWS S3

Endpoints

  • [POST] /api/v1/issues

Description: Create a new issue report.

Request:

{
  "title": "Broken streetlight on main road",
  "category": "road works",
  "description": "There's a deep pothole in the middle of Maple Drive...",
  "location": {
    "latitude": 55.9533,
    "longitude": -3.1883,
    "city": "Edinburgh",
    "county": "Yorkshire"
  },
  "pictures": ["picture1", "picture2"]
}

Response:

{
  "issue_id": 123,
  "message": "Issue report created successfully."
}

Notes:

  • Insert issue into the issue table
  • Insert pictures into the pictures table

-[GET] /api/v1/issues

Description: GET all issues report.

Request Params:

radius=10
limit=15
type=Enum(Road works/etc..)
latitude = 55.123
logtitute = -3.12

Response:

[
  {
  "id": 123,
  "title": "Broken streetlight on main road",
  "description": "There's a deep pothole in the middle of Maple Drive...",
  "category": "road works",
  "pictures": ["picture1", "picture2"],
  "location": {
    "latitude": 55.9533,
    "longitude": -3.1883,
    "city": "Edinburgh",
    "county": "Yorkshire"
  },
  "created_at": "2024-11-03T10:30:00Z",
  "upvotes": 1000
  },
]

Notes:

  • Filter the list of issues within the radius of the specific location, with a limit and types.

  • Use PostGIS extension for these issues.

SELECT id, title, description, category, pictures, location, created_at, upvotes
FROM issues
WHERE ST_DWithin(
    location,
    ST_MakePoint(-3.12, 55.123)::geography,  -- User's longitude and latitude
    10000                                      -- Radius in meters (10 km here)
)
AND category = 'road works'                     -- Filter by type if provided
ORDER BY created_at DESC
LIMIT 15;
  • [GET] /api/v1/issues/{issue_id}

Description: Retrieve details of a specific issue.

Response:

{
  "id": 123,
  "title": "Broken streetlight on main road",
  "description": "There's a deep pothole in the middle of Maple Drive...",
  "category": "road works",
  "pictures": ["picture1", "picture2"],
  "location": {
    "city": "Edinburgh",
    "county": "Yorkshire"
  },
  "created_at": "2024-11-03T10:30:00Z",
  "upvotes": 1000
}

Notes:

Load the comments and users who left this comment, for the specific issue

Views

1. Create Issue

class CreateIssueView(APIView):
    permission_classes = [permissions.IsAuthenticated, isCitizen]

    def post(self, request):
        # 1. Get the issue data from request.FILES.getlist
        # 2. Serialize results of the issue
        # 3. Add images to the separate table

2. Retrieve Issues

class RetrieveIssuesView(APIView):
    permission_classes = [permissions.AllowAny]

    def get(self, request):
        # 1. Get  latitude , longitude , radius , category from the request param for  filtration
        # 2. Filter with Geoposix query
        # 3. Return serialized results of the issue with loaded pictures  

3. Retrieve Issue Details

class RetrieveIssueDetailView(APIView):
    permission_classes = [permissions.AllowAny]

    def get(self, request, id):
        # 1. Get the issue by id 
        # 2. Serialize issue and prefetch images 

AWS Integration

  1. File Upload to S3

    class S3Service:
    
        def upload_file(self, file, folder, user_id):
            file_name = f"{folder}/{user_id}/{uuid.uuid4()}_{file.name}"
            self.s3.upload_fileobj(file, BUCKET_NAME, file_name, ExtraArgs={"ACL": "public-read"})
            return f"https://{BUCKET_NAME}.s3.amazonaws.com/{file_name}"
    

  2. Storing URLs in the IssueImage Table

  3. Save the returned S3 URL in the photo column.

  4. Retrieve Images

  5. Query the IssueImage table for all images related to a specific issue.

Tests

Name Purpose
test_create_issue Tests issue creation, including AWS upload.
test_retrieve_issues Tests retrieval of issues with filters.
test_retrieve_issue_detail Tests detailed issue retrieval with images.

Checklist

  • Implement database migrations for issue and issue_image tables.
  • Integrate PostGIS for geospatial queries.
  • Implement file uploads to AWS S3.
  • Write unit tests for all endpoints.
  • Update API documentation with new endpoints.