diff --git a/api/migrations/0013_food_user.py b/api/migrations/0013_food_user.py new file mode 100644 index 0000000..43bb605 --- /dev/null +++ b/api/migrations/0013_food_user.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.3 on 2024-06-15 21:57 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0012_food_archived_food_image_url'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='food', + name='user', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/api/migrations/0014_food_follow_up_food_response.py b/api/migrations/0014_food_follow_up_food_response.py new file mode 100644 index 0000000..016a26c --- /dev/null +++ b/api/migrations/0014_food_follow_up_food_response.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.3 on 2024-06-15 22:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0013_food_user'), + ] + + operations = [ + migrations.AddField( + model_name='food', + name='follow_up', + field=models.TextField(blank=True, null=True), + ), + migrations.AddField( + model_name='food', + name='response', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/api/models.py b/api/models.py index 6c19e89..6d19f88 100644 --- a/api/models.py +++ b/api/models.py @@ -27,7 +27,10 @@ class Food(models.Model): """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=255, default="") + user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False) initial_description = models.TextField(blank=True, null=True) + response = models.TextField(blank=True, null=True) + follow_up = models.TextField(blank=True, null=True) archived = models.BooleanField(default=False) image_url = models.URLField(blank=True, null=True) # the system with nutritional info is on a *range* of values, so we need to store the min and max values diff --git a/api/urls.py b/api/urls.py index 6fb0fc8..91d6306 100644 --- a/api/urls.py +++ b/api/urls.py @@ -2,7 +2,7 @@ from rest_framework.authtoken.views import obtain_auth_token from api.views import LogFood, GetMealsAndDetails, GetFoodDetails, Apple_CreateAccount, UserExists, \ - Apple_GetUserToken, SaveFood + Apple_GetUserToken, SaveFood, GetFoods urlpatterns = [ path('get-reg-user-token/', obtain_auth_token, name="api_token_auth"), @@ -13,4 +13,5 @@ path('save-food/', SaveFood.as_view(), name='save_food'), path('meals/', GetMealsAndDetails.as_view(), name='get_meal_info'), path('food//', GetFoodDetails.as_view(), name='get_food_details'), + path('get-foods/', GetFoods.as_view(), name='get_foods_from_ids') ] diff --git a/api/views.py b/api/views.py index 8bc48ba..6ebe1f4 100644 --- a/api/views.py +++ b/api/views.py @@ -1,5 +1,6 @@ from typing import Optional +import requests from django.contrib.auth.models import User from rest_framework import status from rest_framework.authtoken.models import Token @@ -33,12 +34,13 @@ class ErrorMessage(APIException): class UserExists(APIView): def get(self, request, *args, **kwargs): + print("Checking if user exists...") user_id: str = self.kwargs.get('user_id') if not user_id: return Response({'message': 'Please provide a user_id'}, status=status.HTTP_400_BAD_REQUEST) if User.objects.filter(username=user_id).exists(): return Response({'exists': True}, status=status.HTTP_200_OK) - return Response({'exists': False}, status=status.HTTP_404_NOT_FOUND) + return Response({'exists': False}, status=status.HTTP_200_OK) class SaveFood(APIView): permission_classes = [IsAuthenticated] @@ -59,54 +61,22 @@ def get(self, request, *args, **kwargs): class LogFood(APIView): permission_classes = [IsAuthenticated] - @dataclass - class RequestType: - """ - For reference only. Not used in the code. - """ - description: str - meal_type: str - date: str # YYYY-MM-DD "2024-05-12" - name: Optional[str] - image: Optional[any] - - @dataclass - class ResponseType: - """ - For reference only. Not used in the code. - """ - response: str - follow_up: str - meal_name: str - calories_min: float - calories_max: float - protein_min: float - protein_max: float - total_fat_min: float - total_fat_max: float - saturated_fat_min: float - saturated_fat_max: float - carbohydrates_min: float - carbohydrates_max: float - sugar_min: float - sugar_max: float - fiber_min: float - fiber_max: float - cholesterol_min: float - cholesterol_max: float - sodium_grams_min: float - sodium_grams_max: float - @staticmethod def add_food_to_meal(user, food: Food, meal_type: str, date: str, meal_name=None) -> Meal: meal, created = Meal.objects.get_or_create(meal_type=meal_type, date=date, user=user) meal.meal_items.add(food) if meal_name: meal.name = meal_name + + if not meal.description: + meal.description = food.initial_description + else: + meal.description += " " + food.initial_description meal.save() return meal def post(self, request): + print("getting response from OpenAI...") user = request.user description = request.data.get("description") meal_type = request.data.get("meal_type") @@ -124,7 +94,7 @@ def post(self, request): raise ErrorMessage("Invalid date format. Please use YYYY-MM-DD format.") name = request.data.get("name") - image = request.FILES.get("image") + image = request.FILES.get("image") # TODO you have to either change this to a form accepted thingy or get the decoded data from the frontend? Or use a url? temperature = 0.1 @@ -162,14 +132,15 @@ def post(self, request): response = json.loads(response) + + + # serialize into database # add extra properties if image_url: response["image_url"] = image_url - response["name"] = name if name else response["name"] - - # serialize into database response["archived"] = True + response["user"] = user.id food_serializer = FoodSerializer(data=response) if food_serializer.is_valid(): @@ -177,6 +148,7 @@ def post(self, request): food.initial_description = description food.save() else: + print(food_serializer.errors) raise ErrorMessage("Error saving food data to database") self.add_food_to_meal(user, food, meal_type, date_str, name) @@ -187,6 +159,28 @@ def post(self, request): return Response(response) +class GetFoods(APIView): + permission_classes = [IsAuthenticated] + + @staticmethod + def get(request): + user = request.user + all_foods = Food.objects.filter(user=user) + food_serializer = FoodSerializer(all_foods, many=True) + return Response(food_serializer.data) + + + @staticmethod + def post(request): + user = request.user + ids_arr: list[str] = request.data.get("ids") + if not ids_arr: + raise ErrorMessage("Please provide an array of ids") + foods = Food.objects.filter(id__in=ids_arr) + food_serializer = FoodSerializer(foods, many=True) + return Response(food_serializer.data) + + class GetFoodDetails(APIView): permission_classes = [IsAuthenticated] @@ -313,6 +307,7 @@ def get(self, request, *args, **kwargs): class Apple_CreateAccount(APIView): @staticmethod def post(request): + print("Creating Apple User...") serializer = CreateUserSerializer(data=request.data) if serializer.is_valid(): username = serializer.validated_data['user_id'] @@ -341,3 +336,43 @@ def post(request): else: return Response({'error': 'Apple User already exists.'}, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class VerifyAppleToken(APIView): + def post(self, request, *args, **kwargs): + user_id = request.data.get('user_id') + identity_token = request.data.get('identity_token') + + if not user_id or not identity_token: + return Response({'message': 'Missing user_id or identity_token'}, status=status.HTTP_400_BAD_REQUEST) + + # Validate the token with Apple + client_id = "YOUR_CLIENT_ID" + client_secret = "YOUR_CLIENT_SECRET" + + response = requests.post( + 'https://appleid.apple.com/auth/token', + data={ + 'client_id': client_id, + 'client_secret': client_secret, + 'code': identity_token, + 'grant_type': 'authorization_code', + } + ) + + if response.status_code != 200: + return Response({'message': 'Invalid token'}, status=response.status_code) + + response_data = response.json() + # TODO: finish this + + # Check if the user exists in the database + try: + user = User.objects.get(username=user_id) + except User.DoesNotExist: + return Response({'message': 'User not found'}, status=status.HTTP_404_NOT_FOUND) + + # Ensure the user has a token + if not hasattr(user, 'auth_token'): + Token.objects.create(user=user) + + return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK) \ No newline at end of file