diff --git a/.flake8 b/.flake8 index bda85de..7c5a074 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] max-line-length = 140 -ignore = E501, W503 \ No newline at end of file +ignore = E501, W503, E203 \ No newline at end of file diff --git a/db-prep/create_bookmarks_table.py b/db-prep/create_bookmarks_table.py new file mode 100644 index 0000000..5cfece5 --- /dev/null +++ b/db-prep/create_bookmarks_table.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 + +import sys +import boto3 + + +def create_bookmarks_table(dynamodb, table_name): + table = dynamodb.create_table( + TableName=table_name, + KeySchema=[{"AttributeName": "BookmarkId", "KeyType": "HASH"}], + AttributeDefinitions=[ + {"AttributeName": "BookmarkId", "AttributeType": "S"}, + {"AttributeName": "UserId", "AttributeType": "S"}, + {"AttributeName": "ServiceId", "AttributeType": "S"}, + ], + GlobalSecondaryIndexes=[ + { + "IndexName": "UserBookmarksIndex", + "KeySchema": [ + {"AttributeName": "UserId", "KeyType": "HASH"}, + {"AttributeName": "ServiceId", "KeyType": "RANGE"}, + ], + "Projection": {"ProjectionType": "ALL"}, + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5, + }, + } + ], + ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}, + ) + + # Wait for the table to be created + table.meta.client.get_waiter("table_exists").wait(TableName=table_name) + print("Bookmarks table has been created successfully.") + return table + + +def main(): + if len(sys.argv) != 2: + print("Usage: python3 create_bookmarks_table.py ") + exit(1) + table_name = sys.argv[1] + dynamodb = boto3.resource("dynamodb", region_name="us-east-1") + create_bookmarks_table(dynamodb, table_name) + + +if __name__ == "__main__": + main() diff --git a/src/accounts/forms.py b/src/accounts/forms.py index ba64086..d978137 100644 --- a/src/accounts/forms.py +++ b/src/accounts/forms.py @@ -39,6 +39,8 @@ def clean(self): if user is None: raise forms.ValidationError("Invalid username/email or password.") else: + if user.user_type != "user": + raise forms.ValidationError("This page is for users only.") self.confirm_login_allowed(user) self.user_cache = user else: diff --git a/src/accounts/templates/profile_base.html b/src/accounts/templates/profile_base.html index 23bb7e9..670061e 100644 --- a/src/accounts/templates/profile_base.html +++ b/src/accounts/templates/profile_base.html @@ -1,76 +1,118 @@ -{% load widget_tweaks %} - - - - - - Service Seeker Profile - - - - -
-

Service Seeker Profile

- - -
-

Username: {{ profile.username }}

-

Email: {{ profile.email }}

-

First Name: {{ profile.first_name }}

-

Last Name: {{ profile.last_name }}

-
    - {% for service in profile.bookmarked_services.all %} -
  • {{ service.title }} by {{ service.provider.business_name }}
  • - {% endfor %} -
+{% extends 'base.html' %} +{% block title %}Service Seeker Profile{% endblock %} +{% block content %} +
+
+

Profile

+

Manage your account information and activities

+
- + +
+
+
+ {{ profile.username|first }} +
+
+

Profile Information

+

Username: {{ profile.username }}

+

Email: {{ profile.email }}

+

First Name: {{ profile.first_name }}

+

Last Name: {{ profile.last_name }}

+
- - +
+{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/src/accounts/views.py b/src/accounts/views.py index a804e8c..5ede6e8 100644 --- a/src/accounts/views.py +++ b/src/accounts/views.py @@ -10,8 +10,11 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.contrib.auth.decorators import login_required +from dateutil.parser import parse as parse_date + from accounts.models import CustomUser +from home.repositories import HomeRepository from .forms import ( ServiceSeekerForm, @@ -50,6 +53,41 @@ def profile_view(request): elif user.user_type == "user": service_seeker = get_object_or_404(CustomUser, email=user.email) + # Fetch user's bookmarks + repo = HomeRepository() + bookmarks = repo.get_user_bookmarks(str(user.id)) + + # Process the bookmarks + processed_bookmarks = [ + { + "Id": item.get("Id"), + "Name": item.get("Name", "No Name"), + "Category": item.get("Category", "N/A"), + "Distance": "N/A", # Calculate if needed + "Address": item.get("Address", "N/A"), + } + for item in bookmarks + ] + + # Fetch user's reviews + reviews = repo.fetch_reviews_by_user(str(user.id)) + + # Get unique service IDs from the reviews + service_ids = set([review["ServiceId"] for review in reviews]) + + # Fetch service details + service_map = repo.get_services_by_ids(service_ids) + + # Add service names and parse timestamps in reviews + for review in reviews: + service = service_map.get(review["ServiceId"], {}) + review["ServiceName"] = service.get("Name", "Unknown Service") + # Parse the timestamp + try: + review["Timestamp"] = parse_date(review["Timestamp"]) + except Exception: + pass # Keep as string if parsing fails + # If it's a POST request, we're updating the profile if request.method == "POST": form = ServiceSeekerForm(request.POST, instance=service_seeker) @@ -65,6 +103,8 @@ def profile_view(request): { "profile": service_seeker, "form": form, # Pass the form to the template + "bookmarks": processed_bookmarks, + "reviews": reviews, # Pass the reviews to the template }, ) @@ -148,6 +188,12 @@ class UserLoginView(CustomLoginView): form_class = UserLoginForm template_name = "user_login.html" + def get_success_url(self): + if self.request.user.user_type == "user": + return reverse_lazy("home") # Redirect to user's home page + else: + return reverse_lazy("user_login") + # Service provider login view class ServiceProviderLoginView(CustomLoginView): diff --git a/src/home/repositories.py b/src/home/repositories.py index 987ee5b..466e055 100644 --- a/src/home/repositories.py +++ b/src/home/repositories.py @@ -16,6 +16,7 @@ def __init__(self): self.reviews_table = self.dynamodb.Table( settings.DYNAMODB_TABLE_REVIEWS ) # Ensure this is set in settings + self.bookmarks_table = self.dynamodb.Table(settings.DYNAMODB_TABLE_BOOKMARKS) def fetch_items_with_filter( self, search_query, category_filter, radius, ulat, ulon @@ -155,3 +156,99 @@ def fetch_reviews_for_service(self, service_id): except ClientError as e: print(f"Error fetching reviews: {e.response['Error']['Message']}") return [] + + def add_bookmark(self, bookmark_id, user_id, service_id): + try: + timestamp = datetime.utcnow().isoformat() + self.bookmarks_table.put_item( + Item={ + "BookmarkId": bookmark_id, + "UserId": user_id, + "ServiceId": service_id, + "timestamp": timestamp, + } + ) + except ClientError as e: + print(f"Failed to add bookmark: {e.response['Error']['Message']}") + raise e + + def remove_bookmark(self, user_id, service_id): + try: + response = self.bookmarks_table.query( + IndexName="UserBookmarksIndex", + KeyConditionExpression=Key("UserId").eq(user_id) + & Key("ServiceId").eq(service_id), + ) + items = response.get("Items", []) + if items: + bookmark_id = items[0]["BookmarkId"] + self.bookmarks_table.delete_item(Key={"BookmarkId": bookmark_id}) + except ClientError as e: + print(f"Failed to remove bookmark: {e.response['Error']['Message']}") + raise e + + def is_bookmarked(self, user_id, service_id): + try: + response = self.bookmarks_table.query( + IndexName="UserBookmarksIndex", + KeyConditionExpression=Key("UserId").eq(user_id) + & Key("ServiceId").eq(service_id), + ) + items = response.get("Items", []) + return len(items) > 0 + except ClientError as e: + print(f"Failed to check bookmark: {e.response['Error']['Message']}") + raise e + + def get_user_bookmarks(self, user_id): + try: + response = self.bookmarks_table.query( + IndexName="UserBookmarksIndex", + KeyConditionExpression=Key("UserId").eq(user_id), + ) + bookmarks = response.get("Items", []) + services = [] + for bookmark in bookmarks: + service_id = bookmark["ServiceId"] + service = self.services_table.get_item(Key={"Id": service_id}).get( + "Item" + ) + if service: + services.append(service) + return services + except ClientError as e: + print(f"Failed to get user bookmarks: {e.response['Error']['Message']}") + raise e + + def fetch_reviews_by_user(self, user_id): + try: + # Since 'UserId' is not the primary key, we need to scan with a filter + response = self.reviews_table.scan( + FilterExpression=Attr("UserId").eq(user_id) + ) + reviews = response.get("Items", []) + # Sort reviews by Timestamp in descending order + reviews.sort(key=lambda x: x["Timestamp"], reverse=True) + return reviews + except ClientError as e: + print(f"Error fetching reviews: {e.response['Error']['Message']}") + return [] + + def get_services_by_ids(self, service_ids): + try: + services = [] + keys = [{"Id": service_id} for service_id in service_ids] + # DynamoDB batch_get_item limit is 100 items per batch + for i in range(0, len(keys), 100): + batch_keys = keys[i : i + 100] + response = self.dynamodb.batch_get_item( + RequestItems={self.services_table.name: {"Keys": batch_keys}} + ) + services.extend( + response.get("Responses", {}).get(self.services_table.name, []) + ) + service_map = {service["Id"]: service for service in services} + return service_map + except ClientError as e: + print(f"Error fetching services: {e.response['Error']['Message']}") + return {} diff --git a/src/home/templates/home.html b/src/home/templates/home.html index cbd9d1d..6b8728d 100644 --- a/src/home/templates/home.html +++ b/src/home/templates/home.html @@ -1,5 +1,6 @@ {% extends 'base.html' %} {% block title %}Home - Public Service Finder{% endblock %} +{% load static %} {% block content %} @@ -29,3 +30,5 @@
{% endblock %} + + diff --git a/src/home/templates/partials/_table.html b/src/home/templates/partials/_table.html index a052648..82db96b 100644 --- a/src/home/templates/partials/_table.html +++ b/src/home/templates/partials/_table.html @@ -1,12 +1,75 @@ +{% load static %} + - {% load static %} + + + + + +