From fa5ba578bb0287920766d3580f0aedc67f221e18 Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Mon, 8 Dec 2025 09:08:32 +0200 Subject: [PATCH] Mapping made_by to displayname --- backend/__pycache__/db_utils.cpython-313.pyc | Bin 8321 -> 8599 bytes backend/__pycache__/main.cpython-313.pyc | Bin 14485 -> 15156 bytes .../__pycache__/user_db_utils.cpython-313.pyc | Bin 4298 -> 4994 bytes backend/db_utils.py | 22 +++++++----- backend/main.py | 16 +++++++++ backend/schema.sql | 2 +- backend/user_db_utils.py | 16 +++++++++ frontend/src/App.jsx | 10 +++--- frontend/src/components/RecipeDetails.jsx | 4 +-- frontend/src/components/RecipeSearchList.jsx | 32 +++++++++++------- 10 files changed, 72 insertions(+), 30 deletions(-) diff --git a/backend/__pycache__/db_utils.cpython-313.pyc b/backend/__pycache__/db_utils.cpython-313.pyc index 5cdd7f3a06d12c0f88b79f29f4644a9453135b0a..e224238e3caf14f1ebfd0487f1f64d008dda74d3 100644 GIT binary patch delta 822 zcmZp4obJr`nU|M~0SF$Knq^Lw*vMzc$(k6!$iOh!k5fXnNG~%*N1;eBFEKY2MCYa^ z=ERp&7JxaEt2tHci}Xqo(~CiJnR)3&sVSMMc_m&5~Q}Y zIJGDqXf&4sc2KI9l384klUNxKvRNUqSRubW52yuM6s&jhHBJc&g(3`dd|cf^6ukUB z{S-h}6)Th~`1>L3Rj^ek1^R6AJ5DL4BE88RTy2c}lLaKjjMxQ5zA$t0@_i6t5D}kl zGtp+UU5CqeaRxr=4-yOlA`dut`nfu}dU?Lf0)={Ge}9nKe4XnWE2G=ybpnOVjQN`t zL~gS%wru_*ai7U7(F7QbC@u?j^>K9$0fiObP*tQss1hBbpa^jR5pF^Lz6wRD$(aSI z#b_Z+T<8|cD9DQ{IQqE&Jq%Acz@S$xu9AcbPhKN4J!By;^%XAy5)BMrI2d?Dx~&>q zJ}^nLihf{X7v$++{UE>~A=Sb5j9cUekKhGSor^p=9j-Tcg!+9ueP?LS*Pp3>SxWl~ zkIpl0VXy*&i#!IKr^^~LNeVKG&JdZAvcPx+^8$;Rc^4&hzc2vBKB!H8D4%IA$0&M( zUwj60XYB`OAo~HI*o5NF+z+fk_6IfwHm(kpj^YW<9TlB=7g;60vIB*Sq=Bvk0G5*U AY5)KL delta 383 zcmbR4+~~;nnU|M~0SH3l%rfi6H}ctWGF6?O9LOm#Sx`V}@){WtW(5U>$yI!slmAIb zvA6~K`%XTCL^JxGr7K2$Vsu%9z8sw^AXln@9RVA$8=;xx4o0^ytUs73+s$i?2T3jU#7My%Y zcDnI=p!bRw0f`2NFB}X!BHdOEE+3f0Sw%lEu?q5ZuzuiSkdW$Nd&VttgGca!sMbXu zt;s!dvMhQRdGt1~l`~|L0EBXVuO diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index be7bf31d86b19d834559fed170cff68a6f31f566..ad86ee89876f1fc9375488beac93c1ec2efa48e6 100644 GIT binary patch delta 2842 zcmai0eQZ~bP?E1*OEXQ5)*j>C%ukg zN!umRt%Pn>fddl}86Q$xLOZEz(?6Wa3hxUNUr zQ=mb8k0z$6MjDg2eoz(AW~FGnKQSD?u^NS0F)`4Y)uvN%-kTW6+J+7$ksC;)hWGW2 z_9pxG$D!3)Wm%7=TurcnAPBn(s{B4=j|dAo!wc;T-inF#$tREXAKf*tu2}E~=hVfg zb?5Cs^&B%3fC7^Z^ zZh;nS_`U!sw0=?%?1U+6E9;;AgEhhQHWaAvxxEoO?S6I`4%)B7YKPNmXkaMD5i>R$ zwu@i;Hgr2mL{lfc;i%UtDZgYOUgR6#nqzZK3Qb4xNxg$GA$(zQ&l@}wn{Sp0w&L4f zvst?3L2j~ju@v5{8r-;0E}u(JrRP>Q&zG-TbQ%lvH{Gbuc?FFzC!l#w7L4|Z&O5g% zaFUYH=_?xxHhR=IK0-Di@_q1mQKQw26D53((Vh~z1lVEl%(fK=nb^gkr@l-_;roPu z3C5gjq%CO9lkk?)YZ@XqYJPwu2jQACsDB>WSMW()2D9tU;)T-cOlj@a(%PBM`O@__ zokpXS6O8bIt3m(OuAj`Zl3r2WB}R+jQjI4Zv(aW1LQwFdBN_u3*IrQ;$^{vJou56d zDi`*$3ZY9CHGo{=Hp;?YAukNr;oZ_0OM}icx)kYQPyHs!Djy@T5;PGg(d+oPh|eDZ zxvaW>D4EQv`qMlsapi;J&yvM+@K4W5!^_CNhEFQWz*sR3uX`{3w?lrttSn#?rdtgW zruvy*7qJOfm_B0GUNLBix7i{-)s2tQSAg;K?Afx5%sqiKeu&^O!3zY+C;A=em0KHL zr1=qoNdhgwQ35K!fRsPUK@z+~@LhuM!SCdt<~Xvyo|P&#Gqx8xDt*f(`z4H4)?mrL zT6rCQTls*Jot<1U#5AT@VayEw@^yNSzI;P8h;qiS}Oe;sqg3^8Mev`w2-$yj)m_?bj4aK>M%Qg^9gRlvziaMkauEsROc zVw`p3Ir1ZBUb+Niq1(PB#nld4U81?i(sGZb6W$LrXf$Z&g1-dpDk177`Vj8Adzwr=P43&r<9!38cYUS9=qPh3^8-0^ zGV?~}^~|ZviOfkkb0%{Z9v zEXx)tDM};<@5=@}5^}GkP38RHtB}v?_}w?aFXGgU<(NvtbDqJ?(Sql0RuZ*L9{jOXH`yCiqFnJzE&A7sK@0_-hFGKD7H2$@EA(wVaZG zm{q0Hdz8atJ9!q8b>51nX-OX~(Q_psN^+H~6p<8Xg{ywd*dh3+zG7V2{D0=3pCdOKet{gnO`zQLLrn+0 zEKc1!s~#T0D^F=R)C;q!;q(ZnI^@(kveH0&UmU-S<0J_Z(AB5BBTnb%@4%^sYW7R` zX~UpV#X^%i7X>7XMyP9qtu|)AtrEogoQPbmMi4beT!&nfHRqpr&pzRrV-H_@w0&;N zlk<;0byKi2>2yzy$a{`OBDAt4CnA@t0pnVauL3P%IRVKG8Y1}>8X_UXrksdeZVNoN zwr{+aRW6vFr&ULv]Ab7AqZ)Y1Jp4e~iju(?nBPN${YPZ*-Ry3N wb9(p8hRZREacfR&V$!h6u;kXd)MAR)^=OhuItuy#jRr-u*q1#DaDlu>d=*mFmP_8nN`+}@3vD= zvw;sb6`3AMP<%f@6p)fYjERcFM>IY{;z-r#A0`I<=fW0^`j6+_I)=u0llwcr?|i@S z-1EK8JwCj5t8Ln9l?8l`CYloE^WU<4Ze*9kcgBW112^{TLK4)Nym`C|6(JMsuoc0x z4jD#FGJq-IDMI%y;jGboU)xz<#c12u%@cj&o4(dpz;~uqtQo50GEWO?I6*U!CW3{U z3vQ7=WG$I_=0cIJhNae**){OJ^#&G&g|@UgN*X=EN;qnBu};`ya?8ufLC^x{Y)Q5p zy!O>DQ>PN=*A4b3lIPY?{|bUuIGKN?yq+k*n#b)WY(2_vWNTr-9xUsk9-3Azn?=Mn z&B9tZV)wF5@TvU?zZPOC^=NUgM%K=?>{gPq4MrUG|Hzi|?4>S(^^kQ$SZ}7(nP9R5 z9Xfoc1F)x{nsGQ;@U1as4$C))*Qel}LXT)^2YXS2cu*z-ZAFn9j{3XsRbD|D6@E0A zoH3V6DaDHv`4{X#i5 z6ba+(iG){J^yfZ78WQ;((B*29ZP?I~>&WbPtzmY`D=364?c9$&d>QzCwQK;cbNj4G z8mH%J@(;tXyH>sj>27?LWAKLiknwzx*&?Ze83sIy%@+e77Ga{GI5X*K6ZJ7iScV0q zKK2D%U%H4nVWc$1QgEhp8-An1*Kjq>nQtSYRpZ*>(Y|Zs_mU&`ab#J2-(WJC)%B%$ zR^su##88}XC&k_Hk#C;)5u`8TtF$usxncx{%MPxYP0t|xRw>xLyEpx*+69w!k325# zw)~+~qWlqiR1}_$m|GX=-mf+ypIjujUX?ewED!l<(HSk&o%;-RA;wdgd&*y9B_r74 zqXat$b`mTjxF1?7B8wj&`XIqBf;@sT0w+R9%1vsR9ODEJ5j+f;idw@2(hoB~RA~Qf6VI=^XWu2N&^&c6!u=$QAU}CWNs(V z#bwPJloU@4@oiwJT2S;D^#utA35E!^!m6qz^-m%F24CeSM#pi+DOlz`zy6Flj7PHU zKaZsAzmDXu0cfs%&KO~MB7eUSgzu}1>m%W)TYt*sj{5Yc8UlzP1j?gT(j@al1NO-h znRvB?csgK+RzWaSCq61>VxgrBp7dM94ja5spF!FTJ*mku+A^jqsxWE3vQ&_flpF@Nf=9~K4vwb*CsTImxT0aQiR!lv^LVA&Cl5m zFZ6f|toGSao%)=|0kXJTh=hd6UkJre3eiH?eKXo1}r4jNO+{N4Z9eob z%FTk3;BEpJK`jB5b}U)@A>CM6PEiy+!d#|?E%g83htRn!bE5HWHZqDdO;M(vojN%6 zI-!5+>8T0-)bq$+otns9@&8?X{tS)}8M0PdobJuN-RV?fi^AVTZ*CKMiD_j`$F<9^ zb?ry5SD|8jaV4exc&d9K&X1sT9k8ZjY%d&dI>Qdb*pfg|3x!!up#80Avb-1GT~c!` z#pd)6$?As&@rvI?ZK~E;-B3Ek>#0d4GAnJ4Z;9hhQMJh}*;B-5-?UZXN8s}2DmDol zoBJ&y3yf`;5zx%Y@I^D^J6OTAP7oVZ5t&*ITf;t|lS%tFssfsmg)?MGaiuCEL&c$R zY0!^~m@1$-fI>7MqYw=Vn^h5+x)Oc~_l(rAz}fubeY){`RUPslY_17qd_XlIS0%w& zvM;z#c{22sBRG?XE*jpbiqyH038G<_dx!f=x#YkV@5M&lU$RxISjVJ^E2afBq+QK4 Zk9zB@c&zS#ft2;C=w=Z%0iP~S`~~tNCZPZT diff --git a/backend/__pycache__/user_db_utils.cpython-313.pyc b/backend/__pycache__/user_db_utils.cpython-313.pyc index c500bba03ee96a69ad65e65a3b506701eacd650e..89b66ff227480f6f8654d7363ab7a0ce5cdb94e9 100644 GIT binary patch delta 287 zcmX@5*rd+)nU|M~0SIDC%`#beC-O-!woFvlU=)}b;3pK!8N+JA6wEKhki}BOozAE! zxN(se|K=#(P(~{W_tX-F(&E%2g``S_l+5CSoWx3nyu{qpA{C%%RpG&|KCaFo3YjT7 zU{xR)9fj1~#LS$@foxKf1^9)ep}OKhatgKzs>OaqhCscVEJaF_uX8G@NTjEh#DlDe zPpXVZR$OESR2B)u#buK(@Mv<};7Pr~E7{>cc|LzIBhTc|{4N?G6-AODLJCAk1BqK4 qHo5sJr8%i~MYcdLBM=vtOpX#r6Z^uz%P9Im0Z6Pc{t9A)H30zAnNB_c delta 83 zcmZotKc&d`nU|M~0SGpwnPn<)PvnzeESspV!N@-`z>g)JQBz>!1}}bgO=dqoKTYn* ivxHnGKNgbYvIc5k1mfb1$!x-DTwfV@8AXevfC2zb01}=6 diff --git a/backend/db_utils.py b/backend/db_utils.py index a8f6141..f2c1881 100644 --- a/backend/db_utils.py +++ b/backend/db_utils.py @@ -54,10 +54,12 @@ def list_recipes_db() -> List[Dict[str, Any]]: with conn.cursor() as cur: cur.execute( """ - SELECT id, name, meal_type, time_minutes, - tags, ingredients, steps, image, made_by, user_id - FROM recipes - ORDER BY id + SELECT r.id, r.name, r.meal_type, r.time_minutes, + r.tags, r.ingredients, r.steps, r.image, r.made_by, r.user_id, + u.display_name as owner_display_name + FROM recipes r + LEFT JOIN users u ON r.user_id = u.id + ORDER BY r.id """ ) rows = cur.fetchall() @@ -163,19 +165,21 @@ def get_recipes_by_filters_db( conn = get_conn() try: query = """ - SELECT id, name, meal_type, time_minutes, - tags, ingredients, steps, image, made_by, user_id - FROM recipes + SELECT r.id, r.name, r.meal_type, r.time_minutes, + r.tags, r.ingredients, r.steps, r.image, r.made_by, r.user_id, + u.display_name as owner_display_name + FROM recipes r + LEFT JOIN users u ON r.user_id = u.id WHERE 1=1 """ params: List = [] if meal_type: - query += " AND meal_type = %s" + query += " AND r.meal_type = %s" params.append(meal_type.lower()) if max_time: - query += " AND time_minutes <= %s" + query += " AND r.time_minutes <= %s" params.append(max_time) with conn.cursor() as cur: diff --git a/backend/main.py b/backend/main.py index a52d7db..ae87be5 100644 --- a/backend/main.py +++ b/backend/main.py @@ -51,6 +51,7 @@ class RecipeCreate(RecipeBase): class Recipe(RecipeBase): id: int user_id: Optional[int] = None # Recipe owner ID + owner_display_name: Optional[str] = None # Owner's display name for filtering class RecipeUpdate(RecipeBase): @@ -132,6 +133,7 @@ def list_recipes(): steps=r["steps"] or [], image=r.get("image"), user_id=r.get("user_id"), + owner_display_name=r.get("owner_display_name"), ) for r in rows ] @@ -157,6 +159,7 @@ def create_recipe(recipe_in: RecipeCreate, current_user: dict = Depends(get_curr steps=row["steps"] or [], image=row.get("image"), user_id=row.get("user_id"), + owner_display_name=current_user.get("display_name"), ) @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 [], image=row.get("image"), 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 [], image=r.get("image"), user_id=r.get("user_id"), + owner_display_name=r.get("owner_display_name"), ) for r in rows ] @@ -288,6 +293,17 @@ def register(user: UserRegister): 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 print(f"[REGISTER] Hashing password...") password_hash = hash_password(user.password) diff --git a/backend/schema.sql b/backend/schema.sql index 88bfd34..78b2138 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS users ( password_hash TEXT NOT NULL, first_name TEXT, last_name TEXT, - display_name TEXT NOT NULL, + display_name TEXT UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); diff --git a/backend/user_db_utils.py b/backend/user_db_utils.py index 264da28..0b4be44 100644 --- a/backend/user_db_utils.py +++ b/backend/user_db_utils.py @@ -84,3 +84,19 @@ def get_user_by_id(user_id: int): finally: cur.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() diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 467216d..0b573d7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -27,7 +27,7 @@ function App() { const [filterMealType, setFilterMealType] = useState(""); const [filterMaxTime, setFilterMaxTime] = useState(""); const [filterTags, setFilterTags] = useState([]); - const [filterMadeBy, setFilterMadeBy] = useState(""); + const [filterOwner, setFilterOwner] = useState(""); // Random recipe filters const [mealTypeFilter, setMealTypeFilter] = useState(""); @@ -126,8 +126,8 @@ function App() { } } - // Filter by made_by - if (filterMadeBy && (!recipe.made_by || recipe.made_by !== filterMadeBy)) { + // Filter by made_by (username) + if (filterOwner && (!recipe.made_by || recipe.made_by !== filterOwner)) { return false; } @@ -343,8 +343,8 @@ function App() { onMaxTimeChange={setFilterMaxTime} filterTags={filterTags} onTagsChange={setFilterTags} - filterMadeBy={filterMadeBy} - onMadeByChange={setFilterMadeBy} + filterOwner={filterOwner} + onOwnerChange={setFilterOwner} /> diff --git a/frontend/src/components/RecipeDetails.jsx b/frontend/src/components/RecipeDetails.jsx index 4df5323..c23dced 100644 --- a/frontend/src/components/RecipeDetails.jsx +++ b/frontend/src/components/RecipeDetails.jsx @@ -35,8 +35,8 @@ function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal,

{translateMealType(recipe.meal_type)} · {recipe.time_minutes} דקות הכנה

- {recipe.made_by && ( -

המתכון של: {recipe.made_by}

+ {(recipe.owner_display_name || recipe.made_by) && ( +

המתכון של: {recipe.owner_display_name || recipe.made_by}

)}
diff --git a/frontend/src/components/RecipeSearchList.jsx b/frontend/src/components/RecipeSearchList.jsx index 4f25f1a..1d02703 100644 --- a/frontend/src/components/RecipeSearchList.jsx +++ b/frontend/src/components/RecipeSearchList.jsx @@ -14,8 +14,8 @@ function RecipeSearchList({ onMaxTimeChange, filterTags, onTagsChange, - filterMadeBy, - onMadeByChange, + filterOwner, + onOwnerChange, }) { const [expandFilters, setExpandFilters] = useState(false); @@ -27,8 +27,14 @@ function RecipeSearchList({ // Extract unique meal types from ALL recipes (not filtered) const mealTypes = Array.from(new Set(allRecipes.map((r) => r.meal_type))).sort(); - // Extract unique made_by from ALL recipes (not filtered) - const allMadeBy = Array.from(new Set(allRecipes.map((r) => r.made_by).filter(Boolean))).sort(); + // Extract unique made_by (username) from ALL recipes and map to display names + 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 const maxTimeInRecipes = Math.max(...allRecipes.map((r) => r.time_minutes), 0); @@ -47,10 +53,10 @@ function RecipeSearchList({ onMealTypeChange(""); onMaxTimeChange(""); onTagsChange([]); - onMadeByChange(""); + onOwnerChange(""); }; - const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterMadeBy; + const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterOwner; return (
@@ -165,18 +171,18 @@ function RecipeSearchList({
- {allMadeBy.map((person) => ( + {allMadeBy.map((madeBy) => ( ))}