Skip to content

Commit

Permalink
[Scalabilty] Implent pagination and category services
Browse files Browse the repository at this point in the history
  • Loading branch information
[esekyi] committed Aug 30, 2024
1 parent fdfc8e0 commit b0fdea2
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 34 deletions.
35 changes: 35 additions & 0 deletions app/services/category_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from app import db
from app.models.category import Category
from sqlalchemy import func

class CategoryService:
@staticmethod
def create_category(name: str, description: str = None) -> Category:
"""
Create a new category.
Args:
name (str): The name of the category.
description (str, optional): The description of the category.
Returns:
Category: The newly created category.
Raises:
ValueError: If a category with the given name already exists (case-insensitive)..
"""
existing_category = Category.query.filter(func.lower(Category.name) == func.lower(name)).first()

if existing_category:
raise ValueError(f"A category with the name '{name}' already exists.")

new_category = Category(name=name, description=description)
db.session.add(new_category)
db.session.commit()

return new_category

@staticmethod
def get_all_categories():
categories = Category.query.order_by(Category.name.asc()).all()
return categories
7 changes: 0 additions & 7 deletions app/services/category_sevice.py

This file was deleted.

31 changes: 24 additions & 7 deletions app/services/image_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,39 @@
from flask import current_app
import uuid
from werkzeug.utils import secure_filename
import logging # Add this import at the top

# Configure logging
logging.basicConfig(level=logging.DEBUG) # Set the logging level to DEBUG



def upload_image_to_s3(image, folder='recipes'):
"""Upload image to S3 bucket and retrieve the given image URL"""

aws_access_key_id = current_app.config['S3_ACCESS_KEY_ID']
aws_secret_access_key = current_app.config['S3_SECRET_ACCESS_KEY']
region_name = current_app.config['S3_REGION']
bucket_name = current_app.config['S3_BUCKET_NAME']

try:

# Generating a random and unique filename for image
filename = secure_filename(image.filename)
unique_filename = f"{uuid.uuid4().hex}_{filename}"
unique_filename = f"{uuid.uuid4().hex}.{filename.rsplit('.', 1)[1].lower()}"

# Init the S3 client
s3_client = boto3.client(
's3',
aws_access_key_id=current_app.config['S3_ACCESS_KEY'],
aws_secret_access_key=current_app.config['S3_SECRET_KEY'],
region_name=current_app.config['S3_REGION']
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
region_name=region_name
)

# Upload the file to S3
s3_client.upload_fileobj(
image,
current_app.config['S3_BUCKET'],
bucket_name,
f"{folder}/{unique_filename}",
ExtraArgs={
"ACL": "public-read",
Expand All @@ -32,11 +44,16 @@ def upload_image_to_s3(image, folder='recipes'):
)

# Return URL of uploaded image
image_url = f"{current_app.config['S3_URL']}{folder}/{unique_filename}"
return image_url
# image_url = f"{current_app.config['S3_URL']}{folder}/{unique_filename}"
return unique_filename


except NoCredentialsError:
logging.error("AWS credentials not available")
raise ValueError("AWS credentials not available")
except ClientError as e:
logging.error(f"Failed to upload image: {e}")
raise ValueError(f"Failed to upload image: {e}")
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
raise ValueError(f"An unexpected error occurred: {e}")
34 changes: 34 additions & 0 deletions app/services/pagination_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from math import ceil

def paginate(items, page, per_page):
"""Paginate a list of items.
Args:
items (list): The list of items to paginate.
page (int): The current page number (1-indexed).
per_page (int): The number of items per page.
Returns:
dict: A dictionary containing paginated items and pagination info.
"""

if page < 1:
raise ValueError("Page number must be 1 or greater.")

total_items = len(items)
total_pages = ceil(total_items / per_page)

if page > total_pages:
raise ValueError("Page number exceeds total pages")

start = (page - 1) * per_page
end = start + per_page
paginated_items = items[start:end]

return {
'items': paginated_items,
'total_items': total_items,
'total_pages': total_pages,
'current_page': page,
'per_page': per_page
}
61 changes: 43 additions & 18 deletions app/services/recipe_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
from app.models.comment import Comment
from app.models.instruction import Instruction
from app import db
import random

from app.services.pagination_service import paginate

def create_recipe(data, user_id, ingredients, comments, instructions, send_url):

def create_recipe(data, user_id, ingredients, instructions, send_url):
"""
Create a new recipe and save it to the database.
Expand All @@ -16,13 +19,18 @@ def create_recipe(data, user_id, ingredients, comments, instructions, send_url):
:return: The newly created Recipe object.
"""
try:
oven_temp = int(data.get('oven_temp')) if data.get('oven_temp') else 60,
prep_time = int(data.get('prep_time')) if data.get('prep_time') else 0
cook_time = int(data.get('cook_time')) if data.get('cook_time') else 0
servings = int(data.get('servings')) if data.get('servings') else 0

new_recipe = Recipe(
title=data['title'],
description=data['description'],
instructions=data['instructions'],
prep_time=int(data.get('prep_time', 0)), # Default to 0 if not provided
cook_time=int(data.get('cook_time', 0)), # Default to 0 if not provided
servings=int(data.get('servings', 0)), # Default to 0 if not provided
oven_temp=oven_temp,
prep_time=prep_time, # Default to 0 if not provided
cook_time=cook_time, # Default to 0 if not provided
servings=servings, # Default to 0 if not provided
category_id=data['category_id'],
user_id=user_id,
image_url=send_url
Expand All @@ -39,16 +47,6 @@ def create_recipe(data, user_id, ingredients, comments, instructions, send_url):
)
db.session.add(ingredient)

# Handle comments
for comment_text in comments:
if comment_text: # not tolerating empty comments
comment = Comment(
text=comment_text,
user_id=user_id,
recipe_id=new_recipe.id
)
db.session.add(comment)

# Handle each instruction
for i, instruction in enumerate(instructions):
if instruction:
Expand All @@ -68,13 +66,40 @@ def create_recipe(data, user_id, ingredients, comments, instructions, send_url):
raise Exception(f"Failed to create recipe: {str(e)}")


def get_all_recipes():
return Recipe.query.all()
def get_all_recipes(page=1, per_page=3):
"""Get all recipes with pagination.
Args:
page (int): The current page number.
per_page (int): The number of recipes per page.
Returns:
dict: A dictionary containing paginated recipes and pagination info.
"""

recipes = Recipe.query.order_by(Recipe.created_at.desc()).all()
return paginate(recipes, page, per_page)


def get_recipe_by_id(recipe_id):
return Recipe.query.get(recipe_id)
return Recipe.query.get_or_404(recipe_id)


def delete_recipe(recipe):
db.session.delete(recipe)


def get_most_viewed_recipes(limit=5):
"""
Check through Recipe table and get the most viewed recipes -
sorted from descending on the view count field. limit of recipes needed
:param int limit, 0"""
return Recipe.query.order_by(Recipe.view_count.desc()).limit(limit).all()


def get_random_recipes_with_images(limit=3):
recipes_with_image = Recipe.query.filter(
Recipe.image_url != None, Recipe.image_url != '').all()
if len(recipes_with_image) > limit:
return random.sample(recipes_with_image, limit)
return recipes_with_image
2 changes: 1 addition & 1 deletion app/services/user_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def get_all_users():


def get_user_by_id(user_id):
return User.query.get(user_id)
return User.query.get_or_404(user_id)


def get_user_by_email(email):
Expand Down
2 changes: 1 addition & 1 deletion app/services/validation_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def validate_recipe_data(data):
errors.append('Title is required.')
if not data.get('description'):
errors.append('Description is required.')
if not data.get('instructions'):
if not data.get('instructions[]'):
errors.append('Instructions are required.')
if not data.get('category_id'):
errors.append('Category is required.')
Expand Down

0 comments on commit b0fdea2

Please sign in to comment.