Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a7ceb96
Added Review-Protokoll-01
GalacticCodeGambit May 12, 2026
42e06bb
Clear participant roles in review protocol
GalacticCodeGambit May 12, 2026
e27f003
Update participants and review focus in documentation
GalacticCodeGambit May 12, 2026
3792403
Made the required RecipeSUCUK changes
PrussianBaron May 18, 2026
5db1e8d
Rename RecipeSucuk.py to RecipeSUCUK.py
PrussianBaron May 18, 2026
4073d82
rezepteanzeige für die rezepte
Nicoolaus May 19, 2026
8e71f2d
Python Black Formatierung umgesetzt
GalacticCodeGambit May 20, 2026
dde8ebd
Fix typo in findRecipes function parameter name
GalacticCodeGambit May 20, 2026
68c92f3
Add import statement for Database module in RecipeSUCUK.py
GalacticCodeGambit May 20, 2026
db157cb
Upgrade actions/checkout from v4 to v5 in CI configuration files
GalacticCodeGambit May 20, 2026
e002531
Correct directory name in README and update email variable for clarity
GalacticCodeGambit May 20, 2026
e7a7603
Improve grammar and punctuation in README description
GalacticCodeGambit May 20, 2026
65c9fc2
Refactor CSS class names for consistency and clarity (#171)
GalacticCodeGambit May 21, 2026
d440801
Upgrade CodeQL action versions from v3 to v4 in CI configuration
GalacticCodeGambit May 22, 2026
e751663
Pin npm dependencies to specific versions for stability and update ve…
GalacticCodeGambit May 22, 2026
4d1a692
Rezeptanzeige ohne match.
Nicoolaus May 22, 2026
c6b08b8
Merge remote-tracking branch 'refs/remotes/origin/blatt18' into feat/…
Nicoolaus May 23, 2026
8d51081
Rezeptanzeige mit match aber auf english
Nicoolaus May 23, 2026
49b8728
rezepteanzeige für die rezepte
Nicoolaus May 27, 2026
fa1d2ab
suchleiste und direkte rezeptanzeige
Nicoolaus May 27, 2026
eb9be01
Merge remote-tracking branch 'origin/Projektabschluss' into feat/Reze…
Nicoolaus May 27, 2026
9b4330d
suchleiste und direkte rezeptanzeige mit allen changes von projektabs…
Nicoolaus May 30, 2026
f6f8440
rezepteanzeige mit popup für zubereitung
Nicoolaus May 31, 2026
c8a9538
Merge remote-tracking branch 'origin/Projektabschluss' into feat/Reze…
Nicoolaus Jun 7, 2026
6cb54d4
Merge remote-tracking branch 'origin/Projektabschluss' into feat/Reze…
Nicoolaus Jun 8, 2026
12c5944
Merge remote-tracking branch 'origin/Projektabschluss' into feat/Reze…
Nicoolaus Jun 8, 2026
3ac7f9b
neue json datei eingebunden und neu gemacht
Nicoolaus Jun 8, 2026
2b003b5
Merge remote-tracking branch 'origin/feat/Rezeptanzeige' into feat/Re…
Nicoolaus Jun 9, 2026
ed9a7ae
Merge remote-tracking branch 'refs/remotes/origin/Projektabschluss' i…
Nicoolaus Jun 10, 2026
3782e87
Merge remote-tracking branch 'refs/remotes/origin/Projektabschluss' i…
Nicoolaus Jun 10, 2026
6c868d6
Merge remote-tracking branch 'refs/remotes/origin/Projektabschluss' i…
Nicoolaus Jun 10, 2026
929fcba
es fucking funktioniert alles: wuhuuuuuuu
Nicoolaus Jun 10, 2026
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
Empty file added project/backend/Auth.py
Empty file.
Empty file added project/backend/Database.py
Empty file.
Empty file added project/backend/EmailService.py
Empty file.
Empty file added project/backend/Ingredient.py
Empty file.
Empty file added project/backend/RecipeSUCUK.py
Empty file.
Empty file added project/backend/Routes.py
Empty file.
Empty file.
3 changes: 2 additions & 1 deletion project/backend/core/Models.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,5 @@ class IngredientSearch(BaseModel):

class RecipeSearchRequest(BaseModel):
zutaten: list[IngredientSearch]
servings: int
servings: int = 1
index: int = 0
23 changes: 12 additions & 11 deletions project/backend/dao/IngredientDAO.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,20 @@ def getAllIngredients() -> list[dict]:


def getIngredientsForRecipe(rid: int) -> list[Ingredient]:
"""Gibt die Zutaten eines Rezepts als Ingredient-Objekte zurück."""
with getDB() as con:
cur = con.cursor()
cur.execute(
"""
SELECT Ingredient.name, Exists_from.amount
FROM Ingredient
JOIN Exists_from ON Ingredient.id = Exists_from.zid
WHERE Exists_from.rid = ?
""",
(rid,),
)
return [Ingredient(row["name"], row["amount"]) for row in cur.fetchall()]
cur.execute("""
SELECT Ingredient.name, Exists_from.amount, Ingredient.amountType
FROM Ingredient
JOIN Exists_from ON Ingredient.id = Exists_from.zid
WHERE Exists_from.rid = ?
""", (rid,))
ingredients = []
for row in cur.fetchall():
ing = Ingredient(row["name"], row["amount"])
ing.setAmountType(row["amountType"])
ingredients.append(ing)
return ingredients


def incrementIngredientUsage(AccountID: int, name: str, unit: str | None) -> None:
Expand Down
54 changes: 54 additions & 0 deletions project/backend/dao/RecipeDAO.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,57 @@ def getAllocatedRecipes(name: str) -> list[dict]:
(name,),
)
return [dict(row) for row in cur.fetchall()]
def getAllRecipesPaginated(offset: int) -> list[dict]:
with getDB() as con:
cur = con.cursor()
cur.execute("""
SELECT r.id, r.name, r.description
FROM Recipe r
ORDER BY r.name
LIMIT 12 OFFSET ?
""", (offset,))
return [dict(row) for row in cur.fetchall()]


def searchRecipesByIngredients(likeConditions: str, likeParams: list, offset: int) -> list[dict]:
with getDB() as con:
cur = con.cursor()
cur.execute(f"""
SELECT
r.id, r.name, r.description,
COUNT(DISTINCT CASE WHEN {likeConditions} THEN i.id END) AS matching,
COUNT(DISTINCT ef.zid) AS total
FROM Recipe r
LEFT JOIN Exists_from ef ON ef.rid = r.id
LEFT JOIN Ingredient i ON i.id = ef.zid
GROUP BY r.id
ORDER BY matching DESC, r.name
LIMIT 12 OFFSET ?
""", (*likeParams, offset))
return [dict(row) for row in cur.fetchall()]

def getAllRecipesWithIngredients() -> list[dict]:
with getDB() as con:
cur = con.cursor()
cur.execute("""
SELECT r.id, r.name, r.description,
i.name as ing_name, ef.amount, i.amountType
FROM Recipe r
LEFT JOIN Exists_from ef ON ef.rid = r.id
LEFT JOIN Ingredient i ON i.id = ef.zid
ORDER BY r.id
""")
rows = cur.fetchall()

recipes = {}
for row in rows:
rid = row["id"]
if rid not in recipes:
recipes[rid] = {"id": rid, "name": row["name"], "description": row["description"], "ingredients": []}
if row["ing_name"]:
recipes[rid]["ingredients"].append({
"name": row["ing_name"],
"amount": row["amount"],
"amountType": row["amountType"]
})
return list(recipes.values())
13 changes: 12 additions & 1 deletion project/backend/domain/recipe.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from domain.ingredient import Ingredient

from dao.RecipeDAO import addRecipe, addIngredientToRecipe
from dao.IngredientDAO import getIngredientByName

class Recipe:
def __init__(self, name: str, ingredients: list[Ingredient], description: str):
Expand All @@ -12,6 +13,16 @@ def __init__(self, name: str, ingredients: list[Ingredient], description: str):
self.__countPersons = 1
self.__matching = 0

def saveInDB(self) -> bool:
rid = addRecipe(self.__name, self.__description, None)
for ingredient in self.__ingredients:
result = getIngredientByName(ingredient.getName())
if not result:
return False
zid = result["id"]
addIngredientToRecipe(zid, rid, ingredient.getAmount())
return True

def getName(self) -> str:
return self.__name

Expand Down
44 changes: 34 additions & 10 deletions project/backend/routes/RecipeRoutes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,63 @@
from core.Auth import getCurrentUser
from dao import AccountDAO, IngredientDAO
from core.Models import User, RecipeSearchRequest
from domain.ingredient import Ingredient
from services.RecipeSUCUK import findRecipes

router = APIRouter()


@router.post("/recipes/search")
async def searchRecipes(
body: RecipeSearchRequest,
currentUser: Annotated[User, Depends(getCurrentUser)],
body: RecipeSearchRequest,
currentUser: Annotated[User, Depends(getCurrentUser)],
):
"""Sucht Rezepte basierend auf den übergebenen Zutaten und trackt die Nutzung."""
account = AccountDAO.getAccountByEmail(currentUser.email)
if account is None:
raise HTTPException(status_code=404, detail="Account nicht gefunden")

for zutat in body.zutaten:
IngredientDAO.incrementIngredientUsage(account["id"], zutat.name, zutat.unit)

# Echte Rezept-Suche
ingredients = [Ingredient(z.name, z.amount) for z in body.zutaten]
index = getattr(body, "index", 0)
recipes = findRecipes(ingredients, body.index)

topRows = IngredientDAO.getTopIngredients(account["id"], limit=5)
topIngredients = [
{"name": r["displayName"], "unit": r["lastUnit"]} for r in topRows
]

# TODO: eigentliche Rezept-Suche implementieren
return {"rezepte": [], "topIngredients": topIngredients}
return {
"rezepte": [
{
"name": r.getName(),
"description": r.getDescription(),
"rating": r.getRating(),
"duration": r.getDuration() if hasattr(r, "getDuration") else "",
"matching": r.getMatching(),
"ingredients": [
{
"name": i.getName(),
"amount": i.getAmount(),
"unit": i.getAmountType() or ""
}
for i in r.getIngredients()
]
}
for r in recipes
],
"topIngredients": topIngredients
}


@router.get("/ingredients/top")
async def getTopIngredientsForUser(
response: Response,
currentUser: Annotated[User, Depends(getCurrentUser)],
limit: int = 5,
response: Response,
currentUser: Annotated[User, Depends(getCurrentUser)],
limit: int = 5,
):
"""Liefert die meistgenutzten Zutaten des aktuellen Users."""
account = AccountDAO.getAccountByEmail(currentUser.email)
if account is None:
raise HTTPException(status_code=404, detail="Account nicht gefunden")
Expand All @@ -52,4 +76,4 @@ async def getTopIngredientsForUser(
rows = IngredientDAO.getTopIngredients(account["id"], limit=limit)
return {
"ingredients": [{"name": r["displayName"], "unit": r["lastUnit"]} for r in rows]
}
}
75 changes: 75 additions & 0 deletions project/backend/seed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import json
import re
import sys
import os

sys.path.insert(0, '/app')
os.chdir('/app')

from dao.RecipeDAO import addRecipe, addIngredientToRecipe
from dao.IngredientDAO import addIngredient, getIngredientByName
from core.Database import getConnection, initDB

def getOrCreateIngredient(name: str) -> int:
existing = getIngredientByName(name)
if existing:
return existing["id"]
return addIngredient(name, "mixed")

def extractIngredientName(raw: str) -> str:
"""Extrahiert den Zutatennamen aus einem String wie '41 g Butter' → 'Butter'"""
# Entferne führende Mengenangaben: Zahlen, Einheiten, Sonderzeichen
cleaned = re.sub(r'^[\d\s.,/]+', '', raw).strip()
# Entferne bekannte Einheiten am Anfang
units = ['g ', 'kg ', 'ml ', 'l ', 'EL ', 'TL ', 'Stück ', 'Prise ',
'tsp ', 'tbsp ', 'cup ', 'oz ', 'lb ', 'cl ']
for unit in units:
if cleaned.startswith(unit):
cleaned = cleaned[len(unit):].strip()
# Alles nach Klammer oder Komma abschneiden
cleaned = re.split(r'[,(]', cleaned)[0].strip()
# Maximal 50 Zeichen
return cleaned[:50].strip()

def main():
initDB()

with open('/app/recipes_imported.json', 'r', encoding='utf-8') as f:
recipes = json.load(f)

print(f"Lade {len(recipes)} Rezepte...")
success = 0
skipped = 0

for recipe in recipes:
name = recipe.get("Name", "").strip()
description = recipe.get("Description", "").strip()
rawIngredients = recipe.get("Ingredients", [])

if not name:
continue

try:
rid = addRecipe(name, description)
except Exception as e:
skipped += 1
continue

for rawIng in rawIngredients:
ingName = extractIngredientName(rawIng)
if not ingName:
continue
try:
zid = getOrCreateIngredient(ingName)
addIngredientToRecipe(zid, rid, 1.0)
except Exception:
pass

success += 1
if success % 50 == 0:
print(f" {success} Rezepte verarbeitet...")

print(f"Fertig! {success} Rezepte importiert, {skipped} übersprungen.")

if __name__ == "__main__":
main()
24 changes: 14 additions & 10 deletions project/backend/services/RecipeSUCUK.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"""

from domain.recipe import Recipe
from dao import RecipeDAO, IngredientDAO
from domain.ingredient import Ingredient
from dao import IngredientDAO, RecipeDAO


def findRecipes(ingredients: list, index: int) -> list[Recipe]:
Expand All @@ -17,7 +18,6 @@ def findRecipes(ingredients: list, index: int) -> list[Recipe]:
recipes.sort(key=lambda r: r.getRating(), reverse=True)
return recipes[12 * index : 12 * (index + 1)]


def getMatchingRecipeNames(searchTerm: str) -> list[Recipe]:
"""Gibt Rezepte zurück, deren Name den Suchbegriff enthält."""
searchTerm = searchTerm.lower()
Expand All @@ -31,17 +31,21 @@ def getMatchingRecipeNames(searchTerm: str) -> list[Recipe]:


def _initRecipes() -> list[Recipe]:
return [
Recipe(
r["name"], IngredientDAO.getIngredientsForRecipe(r["id"]), r["description"]
)
for r in RecipeDAO.getAllRecipes()
]
rows = RecipeDAO.getAllRecipesWithIngredients()
result = []
for r in rows:
ingredients = []
for i in r["ingredients"]:
ing = Ingredient(i["name"], i["amount"])
ing.setAmountType(i["amountType"])
ingredients.append(ing)
result.append(Recipe(r["name"], ingredients, r["description"] or ""))
return result


def _scoreRecipes(recipes: list[Recipe], ingredient) -> None:
for recipe in recipes:
for recipeIngredient in recipe.getIngredients():
if recipeIngredient.getName() == ingredient.getName():
if ingredient.getName() in recipeIngredient.getName():
recipe.incrementMatching()
recipe.setRating(recipe.getMatching() / len(recipe.getIngredients()))
recipe.setRating(recipe.getMatching() / len(recipe.getIngredients()))
Loading