Skip to content

Commit

Permalink
Merge branch 'main' into pr/main/176
Browse files Browse the repository at this point in the history
  • Loading branch information
uocli authored Dec 8, 2024
2 parents 22c33c7 + ef14b33 commit 07d2bab
Show file tree
Hide file tree
Showing 36 changed files with 919 additions and 205 deletions.
37 changes: 37 additions & 0 deletions backend/api/custom_views/background_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from ..utils.unsplash import UnsplashAPI


class RandomRecipeImageView(APIView):
authentication_classes = [] # No authentication required
permission_classes = [] # No permissions required

def get(self, request):
try:
image_url = UnsplashAPI.get_random_image(keyword="recipe")
if image_url:
return Response(
{
"success": True,
"imageUrl": image_url,
},
status=status.HTTP_200_OK,
)
else:
return Response(
{
"success": False,
"error": "No image found",
},
status=status.HTTP_200_OK,
)
except Exception as e:
return Response(
{
"success": False,
"error": str(e),
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
2 changes: 1 addition & 1 deletion backend/api/custom_views/password_change_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ def post(self, request, format=None):
data["email"] = user.email
new_request = Request(request._request)
new_request._full_data = data
response = PasswordForgotView().post(new_request, format)
response = PasswordForgotView(ignore_captcha=True).post(new_request, format)
return response
37 changes: 36 additions & 1 deletion backend/api/custom_views/password_forgot_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,17 @@

from ..custom_models.token_model import Token
from ..serializers.token_serializer import TokenSerializer
from ..utils.captcha_utils import verify_captcha
from ..utils.email_utils import send_email


class PasswordForgotView(APIView):
ignore_captcha = False

def __init__(self, ignore_captcha=False):
super(PasswordForgotView, self).__init__()
self.ignore_captcha = ignore_captcha

def get(self, request, format=None):
return Response(
{
Expand All @@ -26,7 +33,35 @@ def get(self, request, format=None):
)

def post(self, request, format=None):
email = request.data["email"]
email = request.data.get("email")

if not email:
return Response(
{
"success": False,
"message": "Email is required!",
},
status=status.HTTP_200_OK,
)
if not self.ignore_captcha:
captcha_token = request.data.get("captcha")
if not captcha_token:
return Response(
{
"success": False,
"message": "CAPTCHA token is required!",
},
status=status.HTTP_200_OK,
)
if not verify_captcha(captcha_token):
return Response(
{
"success": False,
"message": "Invalid CAPTCHA!",
},
status=status.HTTP_200_OK,
)

user = User.objects.filter(email=email).first()
if user is None:
return Response(
Expand Down
14 changes: 13 additions & 1 deletion backend/api/custom_views/recipe_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,19 @@ def post(self, request, uuid):
defaults={"rating": rating_value},
)

return Response({"message": "Rating updated successfully"})
# Reuse RecipeListView to get the updated list of recipes
recipes_view = RecipeListView()
response = recipes_view.get(request)
updated_recipes = response.data

return Response(
{
"success": True,
"message": "Rating updated successfully",
"recipes": updated_recipes,
},
status=status.HTTP_200_OK,
)
except PublicRecipe.DoesNotExist:
return Response(
{"message": "Recipe could not be found!"},
Expand Down
4 changes: 2 additions & 2 deletions backend/api/templates/emails/base_email.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
}

.header {
background: linear-gradient(90deg, #1876D1, #1A8CD1);
background: linear-gradient(45deg, #FE6B8B, #FF8E53);
color: white;
padding: 20px 0;
text-align: center;
Expand All @@ -43,7 +43,7 @@
a.button {
display: inline-block;
padding: 10px 20px;
background-color: #1876D1;
background-color: #FE6B8B;
color: white;
text-decoration: none;
border-radius: 5px;
Expand Down
2 changes: 2 additions & 0 deletions backend/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.urls import path, include

from .custom_views.recipe_views import RecipeListView, RecipeDetailView, RecipeRateView, RecipeCreateView
from .custom_views.background_views import RandomRecipeImageView
from .custom_views.user_profile_views import UserProfileView
from .custom_views.favorites_view import FavoriteListView, FavoriteDeleteView
from .custom_views.shopping_list_views import ShoppingListView
Expand All @@ -22,5 +23,6 @@
path("recipes/", RecipeListView.as_view(), name="recipe-list"),
path("recipe/<str:uuid>/", RecipeDetailView.as_view(), name="recipe-detail"),
path("recipe/<str:uuid>/rate/", RecipeRateView.as_view(), name="recipe-detail"),
path("background-image/", RandomRecipeImageView.as_view(), name="background-image"),
]

14 changes: 14 additions & 0 deletions backend/api/utils/captcha_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import requests
from backend import settings


def verify_captcha(captcha_token):
response = requests.post(
settings.TURNSTILE_VERIFY_URL,
data={
"secret": settings.TURNSTILE_SECRET_KEY,
"response": captcha_token,
},
)
result = response.json()
return result.get("success", False)
20 changes: 20 additions & 0 deletions backend/api/utils/unsplash.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,23 @@ def search_photos(cls, keyword, page=1, per_page=1, size="regular"):
except requests.exceptions.RequestException as e:
print(f"Error fetching Unsplash data: {e}")
return None

@classmethod
def get_random_image(cls, keyword, size="regular", count=1):
endpoint = urljoin(cls.base_url, "photos/random")
params = {
"client_id": cls.access_key,
"query": quote(keyword),
"count": count,
}

try:
response = requests.get(endpoint, params=params)
response.raise_for_status() # Raise HTTPError for bad responses (4xx and 5xx)
data = response.json()
if len(data) > 0:
return data[0]["urls"][size]
return None
except requests.exceptions.RequestException as e:
print(f"Error fetching Unsplash data: {e}")
return None
11 changes: 11 additions & 0 deletions backend/auth/custom_views/login_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken

from api.utils.captcha_utils import verify_captcha
from backend import settings
from ..serializers.user_serializer import UserSerializer

Expand All @@ -15,6 +16,8 @@ class LoginView(APIView):
def post(self, request, format=None):
email = request.data["email"]
password = request.data["password"]
captcha_token = request.data.get("captcha")

if email is None or email.strip() == "":
return Response(
{
Expand All @@ -31,6 +34,14 @@ def post(self, request, format=None):
},
status=status.HTTP_200_OK,
)
if captcha_token is None or not verify_captcha(captcha_token):
return Response(
{
"success": False,
"message": "Invalid CAPTCHA!",
},
status=status.HTTP_200_OK,
)

email = email.strip().lower()
user = User.objects.filter(username=email).first()
Expand Down
21 changes: 17 additions & 4 deletions backend/auth/custom_views/registration_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from api.custom_models.token_model import Token
from backend import settings
from api.serializers.token_serializer import TokenSerializer
from api.utils.captcha_utils import verify_captcha
from api.utils.email_utils import send_email


Expand All @@ -32,15 +33,27 @@ def get(self, request, format=None):
)

def post(self, request, format=None):
email = request.data["email"]
if email is None or email.strip() == "":
email = request.data.get("email")
captcha_token = request.data.get("captcha")

if not email or not captcha_token:
return Response(
{
"success": False,
"message": "Email is required!",
"message": "Email and CAPTCHA token are required!",
},
status=status.HTTP_400_BAD_REQUEST,
status=status.HTTP_200_OK,
)

if not verify_captcha(captcha_token):
return Response(
{
"success": False,
"message": "Invalid CAPTCHA!",
},
status=status.HTTP_200_OK,
)

email = email.strip().lower()
user = User.objects.filter(email=email).first()
if user is not None:
Expand Down
28 changes: 14 additions & 14 deletions backend/backend/favicon_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,59 +9,59 @@

urlpatterns = [
path(
"favicon.ico",
"site.webmanifest",
serve,
{
"document_root": favicon_dir,
"path": "favicon.ico",
"path": "site.webmanifest",
},
),
path(
"site.webmanifest",
"favicon.ico",
serve,
{
"document_root": favicon_dir,
"path": "site.webmanifest",
"path": "favicon.ico",
},
),
path(
"favicon-16x16.png",
"favicon.svg",
serve,
{
"document_root": favicon_dir,
"path": "favicon-16x16.png",
"path": "favicon.svg",
},
),
path(
"favicon-32x32.png",
"apple-touch-icon.png",
serve,
{
"document_root": favicon_dir,
"path": "favicon-32x32.png",
"path": "apple-touch-icon.png",
},
),
path(
"apple-touch-icon.png",
"favicon-96x96.png",
serve,
{
"document_root": favicon_dir,
"path": "apple-touch-icon.png",
"path": "favicon-96x96.png",
},
),
path(
"android-chrome-192x192.png",
"web-app-manifest-192x192.png",
serve,
{
"document_root": favicon_dir,
"path": "android-chrome-192x192.png",
"path": "web-app-manifest-192x192.png",
},
),
path(
"android-chrome-512x512.png",
"web-app-manifest-512x512.png",
serve,
{
"document_root": favicon_dir,
"path": "android-chrome-512x512.png",
"path": "web-app-manifest-512x512.png",
},
),
]
4 changes: 4 additions & 0 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,7 @@
# Unsplash API
UNSPLASH_ACCESS_KEY = os.getenv("UNSPLASH_ACCESS_KEY", "")
UNSPLASH_BASE_URL = os.getenv("UNSPLASH_BASE_URL", "")

# Turnstile settings
TURNSTILE_SECRET_KEY = os.getenv("TURNSTILE_SECRET_KEY", "")
TURNSTILE_VERIFY_URL = os.getenv("TURNSTILE_VERIFY_URL", "")
11 changes: 11 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@marsidev/react-turnstile": "^1.1.0",
"@mui/icons-material": "^6.1.3",
"@mui/material": "^6.1.3",
"@testing-library/jest-dom": "^5.17.0",
Expand Down Expand Up @@ -47,4 +48,4 @@
"engines": {
"node": "16.x"
}
}
}
Binary file removed frontend/public/favicon/android-chrome-192x192.png
Binary file not shown.
Binary file removed frontend/public/favicon/android-chrome-512x512.png
Binary file not shown.
Binary file modified frontend/public/favicon/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed frontend/public/favicon/favicon-16x16.png
Binary file not shown.
Binary file removed frontend/public/favicon/favicon-32x32.png
Binary file not shown.
Binary file added frontend/public/favicon/favicon-96x96.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/public/favicon/favicon.ico
Binary file not shown.
3 changes: 3 additions & 0 deletions frontend/public/favicon/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 07d2bab

Please sign in to comment.