Mapping made_by to displayname

This commit is contained in:
dvirlabs 2025-12-08 09:08:32 +02:00
parent 53ca792988
commit fa5ba578bb
10 changed files with 72 additions and 30 deletions

View File

@ -54,10 +54,12 @@ def list_recipes_db() -> List[Dict[str, Any]]:
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute( cur.execute(
""" """
SELECT id, name, meal_type, time_minutes, SELECT r.id, r.name, r.meal_type, r.time_minutes,
tags, ingredients, steps, image, made_by, user_id r.tags, r.ingredients, r.steps, r.image, r.made_by, r.user_id,
FROM recipes u.display_name as owner_display_name
ORDER BY id FROM recipes r
LEFT JOIN users u ON r.user_id = u.id
ORDER BY r.id
""" """
) )
rows = cur.fetchall() rows = cur.fetchall()
@ -163,19 +165,21 @@ def get_recipes_by_filters_db(
conn = get_conn() conn = get_conn()
try: try:
query = """ query = """
SELECT id, name, meal_type, time_minutes, SELECT r.id, r.name, r.meal_type, r.time_minutes,
tags, ingredients, steps, image, made_by, user_id r.tags, r.ingredients, r.steps, r.image, r.made_by, r.user_id,
FROM recipes u.display_name as owner_display_name
FROM recipes r
LEFT JOIN users u ON r.user_id = u.id
WHERE 1=1 WHERE 1=1
""" """
params: List = [] params: List = []
if meal_type: if meal_type:
query += " AND meal_type = %s" query += " AND r.meal_type = %s"
params.append(meal_type.lower()) params.append(meal_type.lower())
if max_time: if max_time:
query += " AND time_minutes <= %s" query += " AND r.time_minutes <= %s"
params.append(max_time) params.append(max_time)
with conn.cursor() as cur: with conn.cursor() as cur:

View File

@ -51,6 +51,7 @@ class RecipeCreate(RecipeBase):
class Recipe(RecipeBase): class Recipe(RecipeBase):
id: int id: int
user_id: Optional[int] = None # Recipe owner ID user_id: Optional[int] = None # Recipe owner ID
owner_display_name: Optional[str] = None # Owner's display name for filtering
class RecipeUpdate(RecipeBase): class RecipeUpdate(RecipeBase):
@ -132,6 +133,7 @@ def list_recipes():
steps=r["steps"] or [], steps=r["steps"] or [],
image=r.get("image"), image=r.get("image"),
user_id=r.get("user_id"), user_id=r.get("user_id"),
owner_display_name=r.get("owner_display_name"),
) )
for r in rows for r in rows
] ]
@ -157,6 +159,7 @@ def create_recipe(recipe_in: RecipeCreate, current_user: dict = Depends(get_curr
steps=row["steps"] or [], steps=row["steps"] or [],
image=row.get("image"), image=row.get("image"),
user_id=row.get("user_id"), user_id=row.get("user_id"),
owner_display_name=current_user.get("display_name"),
) )
@app.put("/recipes/{recipe_id}", response_model=Recipe) @app.put("/recipes/{recipe_id}", response_model=Recipe)
@ -192,6 +195,7 @@ def update_recipe(recipe_id: int, recipe_in: RecipeUpdate, current_user: dict =
steps=row["steps"] or [], steps=row["steps"] or [],
image=row.get("image"), image=row.get("image"),
user_id=row.get("user_id"), user_id=row.get("user_id"),
owner_display_name=current_user.get("display_name"),
) )
@ -239,6 +243,7 @@ def random_recipe(
steps=r["steps"] or [], steps=r["steps"] or [],
image=r.get("image"), image=r.get("image"),
user_id=r.get("user_id"), user_id=r.get("user_id"),
owner_display_name=r.get("owner_display_name"),
) )
for r in rows for r in rows
] ]
@ -288,6 +293,17 @@ def register(user: UserRegister):
detail="האימייל כבר רשום במערכת" detail="האימייל כבר רשום במערכת"
) )
# Check if display_name already exists
print(f"[REGISTER] Checking if display_name exists...")
from user_db_utils import get_user_by_display_name
existing_display_name = get_user_by_display_name(user.display_name)
if existing_display_name:
print(f"[REGISTER] Display name already exists")
raise HTTPException(
status_code=400,
detail="שם התצוגה כבר קיים במערכת"
)
# Hash password and create user # Hash password and create user
print(f"[REGISTER] Hashing password...") print(f"[REGISTER] Hashing password...")
password_hash = hash_password(user.password) password_hash = hash_password(user.password)

View File

@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS users (
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,
first_name TEXT, first_name TEXT,
last_name TEXT, last_name TEXT,
display_name TEXT NOT NULL, display_name TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );

View File

@ -84,3 +84,19 @@ def get_user_by_id(user_id: int):
finally: finally:
cur.close() cur.close()
conn.close() conn.close()
def get_user_by_display_name(display_name: str):
"""Get user by display name"""
conn = get_db_connection()
cur = conn.cursor(cursor_factory=RealDictCursor)
try:
cur.execute(
"SELECT id, username, email, display_name, created_at FROM users WHERE display_name = %s",
(display_name,)
)
user = cur.fetchone()
return dict(user) if user else None
finally:
cur.close()
conn.close()

View File

@ -27,7 +27,7 @@ function App() {
const [filterMealType, setFilterMealType] = useState(""); const [filterMealType, setFilterMealType] = useState("");
const [filterMaxTime, setFilterMaxTime] = useState(""); const [filterMaxTime, setFilterMaxTime] = useState("");
const [filterTags, setFilterTags] = useState([]); const [filterTags, setFilterTags] = useState([]);
const [filterMadeBy, setFilterMadeBy] = useState(""); const [filterOwner, setFilterOwner] = useState("");
// Random recipe filters // Random recipe filters
const [mealTypeFilter, setMealTypeFilter] = useState(""); const [mealTypeFilter, setMealTypeFilter] = useState("");
@ -126,8 +126,8 @@ function App() {
} }
} }
// Filter by made_by // Filter by made_by (username)
if (filterMadeBy && (!recipe.made_by || recipe.made_by !== filterMadeBy)) { if (filterOwner && (!recipe.made_by || recipe.made_by !== filterOwner)) {
return false; return false;
} }
@ -343,8 +343,8 @@ function App() {
onMaxTimeChange={setFilterMaxTime} onMaxTimeChange={setFilterMaxTime}
filterTags={filterTags} filterTags={filterTags}
onTagsChange={setFilterTags} onTagsChange={setFilterTags}
filterMadeBy={filterMadeBy} filterOwner={filterOwner}
onMadeByChange={setFilterMadeBy} onOwnerChange={setFilterOwner}
/> />
</section> </section>

View File

@ -35,8 +35,8 @@ function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal,
<p className="recipe-subtitle"> <p className="recipe-subtitle">
{translateMealType(recipe.meal_type)} · {recipe.time_minutes} דקות הכנה {translateMealType(recipe.meal_type)} · {recipe.time_minutes} דקות הכנה
</p> </p>
{recipe.made_by && ( {(recipe.owner_display_name || recipe.made_by) && (
<h4 className="recipe-made-by">המתכון של: {recipe.made_by}</h4> <h4 className="recipe-made-by">המתכון של: {recipe.owner_display_name || recipe.made_by}</h4>
)} )}
</div> </div>
<div className="pill-row"> <div className="pill-row">

View File

@ -14,8 +14,8 @@ function RecipeSearchList({
onMaxTimeChange, onMaxTimeChange,
filterTags, filterTags,
onTagsChange, onTagsChange,
filterMadeBy, filterOwner,
onMadeByChange, onOwnerChange,
}) { }) {
const [expandFilters, setExpandFilters] = useState(false); const [expandFilters, setExpandFilters] = useState(false);
@ -27,8 +27,14 @@ function RecipeSearchList({
// Extract unique meal types from ALL recipes (not filtered) // Extract unique meal types from ALL recipes (not filtered)
const mealTypes = Array.from(new Set(allRecipes.map((r) => r.meal_type))).sort(); const mealTypes = Array.from(new Set(allRecipes.map((r) => r.meal_type))).sort();
// Extract unique made_by from ALL recipes (not filtered) // Extract unique made_by (username) from ALL recipes and map to display names
const allMadeBy = Array.from(new Set(allRecipes.map((r) => r.made_by).filter(Boolean))).sort(); const madeByMap = new Map();
allRecipes.forEach((r) => {
if (r.made_by && r.owner_display_name) {
madeByMap.set(r.made_by, r.owner_display_name);
}
});
const allMadeBy = Array.from(madeByMap.keys()).sort();
// Extract max time for slider from ALL recipes (not filtered) - add 10 to make it comfortable to select max time recipes // Extract max time for slider from ALL recipes (not filtered) - add 10 to make it comfortable to select max time recipes
const maxTimeInRecipes = Math.max(...allRecipes.map((r) => r.time_minutes), 0); const maxTimeInRecipes = Math.max(...allRecipes.map((r) => r.time_minutes), 0);
@ -47,10 +53,10 @@ function RecipeSearchList({
onMealTypeChange(""); onMealTypeChange("");
onMaxTimeChange(""); onMaxTimeChange("");
onTagsChange([]); onTagsChange([]);
onMadeByChange(""); onOwnerChange("");
}; };
const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterMadeBy; const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterOwner;
return ( return (
<section className="panel secondary recipe-search-list"> <section className="panel secondary recipe-search-list">
@ -165,18 +171,18 @@ function RecipeSearchList({
<label className="filter-label">המתכונים של:</label> <label className="filter-label">המתכונים של:</label>
<div className="filter-options"> <div className="filter-options">
<button <button
className={`filter-btn ${filterMadeBy === "" ? "active" : ""}`} className={`filter-btn ${filterOwner === "" ? "active" : ""}`}
onClick={() => onMadeByChange("")} onClick={() => onOwnerChange("")}
> >
הכל הכל
</button> </button>
{allMadeBy.map((person) => ( {allMadeBy.map((madeBy) => (
<button <button
key={person} key={madeBy}
className={`filter-btn ${filterMadeBy === person ? "active" : ""}`} className={`filter-btn ${filterOwner === madeBy ? "active" : ""}`}
onClick={() => onMadeByChange(person)} onClick={() => onOwnerChange(madeBy)}
> >
{person} {madeByMap.get(madeBy) || madeBy}
</button> </button>
))} ))}
</div> </div>