Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/api/api_v1/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ def map_recipe_to_response(recipe: Recipe) -> RecipeResponse:
total_calories=recipe.total_calories,
total_quantity=recipe.total_quantity,
is_saved=getattr(recipe, "is_saved", False),
username=(
getattr(recipe.user, "username", None)
if hasattr(recipe, "user") and recipe.user
else None
),
)
19 changes: 19 additions & 0 deletions app/api/api_v1/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ async def get_recipes_with_products(
return [map_recipe_to_response(recipe) for recipe in recipe_list]


@router.get("/", response_model=List[RecipeResponse])
async def get_public_recipes(
session: AsyncSession = Depends(db_helper.session_getter),
current_user: User = Depends(current_active_user_bearer),
):
"""Возвращает список рецептов с продуктами"""
recipe_list = await recipes.get_recipes_with_products(session=session)

if current_user:
saved_recipe_ids = await saved_recipes.get_saved_recipe_ids(
session,
current_user.id,
)
for recipe in recipe_list:
recipe.is_saved = recipe.id in saved_recipe_ids

return [map_recipe_to_response(recipe) for recipe in recipe_list]


@router.post(
"/",
response_model=RecipeResponse,
Expand Down
24 changes: 16 additions & 8 deletions app/api/api_v1/saved_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ async def get_my_saved_recipes(
):
"""Возвращает сохраненные рецепты текущего пользователя"""

saved_recipes_list = await saved_recipes.get_saved_recipes(
session=session,
user_id=current_user.id,
)
try:
saved_recipes_list = await saved_recipes.get_saved_recipes(
session=session,
user_id=current_user.id,
)

recipes = [saved_recipe.recipe for saved_recipe in saved_recipes_list]
return [map_recipe_to_response(recipe) for recipe in recipes]

return [
map_recipe_to_response(saved_recipe.recipe)
for saved_recipe in saved_recipes_list
]
except Exception as e:
print(f"Error in get_my_saved_recipes: {e}")
raise HTTPException(status_code=500, detail="Internal server error")


@router.post(
Expand Down Expand Up @@ -99,3 +102,8 @@ async def check_recipe_saved(
)

return {"is_saved": is_saved}


@router.get("/test")
async def test_endpoint():
return {"message": "Saved recipes router is working!"}
2 changes: 1 addition & 1 deletion app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ApiV1Prefix(BaseModel):
messages: str = "/messages"
products: str = "/products"
recipes: str = "/recipes"
saved_recipes: str = "/saved_recipes"
saved_recipes: str = "/saved-recipes"


class ApiPrefix(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions app/core/schemas/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class RecipeResponse(BaseModel):
total_calories: int
total_quantity: int
is_saved: bool = False
username: Optional[str] = None


class RecipeUpdateRequest(BaseModel):
Expand Down
5 changes: 4 additions & 1 deletion app/crud/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ async def get_recipes_with_products(

stmt = (
select(Recipe)
.options(selectinload(Recipe.product_associations))
.options(
selectinload(Recipe.product_associations),
selectinload(Recipe.user),
)
.order_by(Recipe.id)
)
result: Result = await session.execute(stmt)
Expand Down
144 changes: 144 additions & 0 deletions app/static/css/templatemo-style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1112,4 +1112,148 @@ address {
background: white;
border: 1px solid #dee2e6;
border-radius: 4px;
}

/* Стили для кнопок сохранения */
.save-recipe-btn {
background: #6c757d;
color: white;
border: none;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
width: 100%;
}

.save-recipe-btn.saved {
background: #28a745;
}

.save-recipe-btn:hover:not(:disabled) {
opacity: 0.9;
transform: translateY(-1px);
}

.save-recipe-btn:disabled {
background: #ccc;
cursor: not-allowed;
}

.recipe-actions {
display: flex;
gap: 10px;
margin-top: 10px;
justify-content: center;
flex-direction: column;
}

.recipe-author {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
font-size: 0.8rem;
color: #666;
}

.recipe-products {
margin-top: 10px;
font-size: 0.9rem;
}

.recipe-products ul {
margin: 5px 0;
padding-left: 20px;
}

.recipe-products li {
margin-bottom: 3px;
font-size: 0.8rem;
}

/* Стили для планировщика */
.recipe-actions {
display: flex;
flex-direction: column; /* Вертикальное расположение */
align-items: center;
gap: 8px;
margin-top: 10px;
}

.action-btn {
border: none;
padding: 10px 12px; /* Уменьшили padding */
border-radius: 5px;
cursor: pointer;
font-size: 0.85rem; /* Уменьшили размер шрифта */
transition: all 0.3s ease;
width: 180px; /* Увеличили ширину */
text-align: center;
white-space: nowrap; /* Запрет переноса текста */
overflow: hidden;
}

.add-to-grocery-btn {
background: #28a745;
color: white;
}

.add-to-grocery-btn:hover {
background: #218838;
transform: translateY(-1px);
}

.unsave-recipe-btn {
background: #dc3545;
color: white;
}

.unsave-recipe-btn:hover {
background: #c82333;
transform: translateY(-1px);
}

/* Стили для главной страницы */
.save-recipe-btn {
background: #6c757d;
color: white;
border: none;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
width: 100%;
margin-top: 10px;
}

.save-recipe-btn.saved {
background: #28a745;
}

.save-recipe-btn:hover:not(:disabled) {
opacity: 0.9;
transform: translateY(-1px);
}

/* Статистика */
.tm-stat-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}

.tm-stat-number {
font-size: 2.5rem;
margin-bottom: 0.5rem;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}

.tm-stat-label {
font-size: 0.9rem;
opacity: 0.9;
font-weight: 500;
color: #ffffff !important;
}
Loading