From e0b0ec94fe243ca30abce25f14b9f93b93acaef0 Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Fri, 5 Dec 2025 15:47:47 +0200 Subject: [PATCH] Add made by --- backend/__pycache__/db_utils.cpython-313.pyc | Bin 8018 -> 8219 bytes backend/__pycache__/main.cpython-313.pyc | Bin 6530 -> 6779 bytes backend/db_utils.py | 19 ++++++---- backend/main.py | 5 +++ backend/schema.sql | 6 ++- frontend/src/App.css | 7 ++++ frontend/src/App.jsx | 8 ++++ frontend/src/components/RecipeDetails.jsx | 3 ++ frontend/src/components/RecipeFormDrawer.jsx | 16 ++++++++ frontend/src/components/RecipeSearchList.jsx | 32 +++++++++++++++- .../templates/db-schema-configmap.yaml | 35 ++++++++++++++++++ 11 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 my-recipes-chart/templates/db-schema-configmap.yaml diff --git a/backend/__pycache__/db_utils.cpython-313.pyc b/backend/__pycache__/db_utils.cpython-313.pyc index feab553507773361b6634f4b06c5cc279f89a60c..dfc8c39f38d5a9184e11d6a89b8f12899571ae05 100644 GIT binary patch delta 775 zcmZXRUr19?9LLYO+gSJR-n;Go^==MF{>`RDOf5^N?)D&tVl$D3#gw_r04 zp^GR;1F*+!OoU9>WS18C|?uWSw(;={|#E z-I7rWO%>x!MX73GV16Le`$1|hNkqOuT}4?y$%@NOI8y~ZinaVhN~ zB4C%#^lC&GY>Y123r@SK6+v&vb-M)$x5JPa zwsnz+30M}NQ9~45au4U8N)m=|lIl3@T<{3Il;2vJ{+N$+=XzNOc9kI1tMRgvw0fFK zw7{Aw<1_G4t309EN3hRA|_jtP3FVdsnzB^ zcYWmb*j8kCYb3fAiN3k@?%Zg}yk%U#!hQ%bE7lSs#%R>YQB5Yf63$FLCgH@*4&|%! R$$Z1=<&x==|Mph3{RPk@=dAz$ delta 741 zcmbR3aLJDEGcPX}0}yyGGRh1W+sJ3n$+&y6A)oN%erfB;34AP*7jVjNKEav7$Y{1% zmpg}%nZbl(@*W;-R?c7`ocxAoHZxag`Q$F%N@lLanVb3f1Q;hf@>fqj${#pcUZ6tS z8fdl)5EtJ85)BMrxEOdux~&>qJ}}8m-Y+4>$}K4JL2B|@f$VzO4}zS6LSLlCdHFsV zFbIoH*O{m@S+B$SyD!R}#gsE4&MJe4bn-BT$9gos!o=WDPnY*+%4nB>H$CKbXldir(NCpTXQ&`+*tAe!wR- Mp|~@5@-KNs0MuO4cK`qY diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index 5140ab0aae1d4e33a9910d6be7435ae4626b3e42..4a21043bdb1eac56569f7f02341e09b13c7bc3d1 100644 GIT binary patch delta 2124 zcma)7T}%{L6rP!#ncex>e|Begg}N*&G8hV?rb0o3af=3}ty>MI5W2FXZo>k(vy`?; z<60Ayr($nY*QBwgPo*!#7!%WnCRO{;v`M$&g-p`ahramGWCUVD`qFb|0nzx-N$z*g zJ?G4s`<;8volp1tyVtkk^~wx9y^lHz--K@a&b!zO?G_v87p}VTtU%nuo?%`9hhZTr z?u5CWA=0qCsdKOElr1{Xx=!7qlTZioyj$YQj!q%IbAqmZ&UeUzmb;kC8>44Fq8)rf zv4lb~cg~n+bU!`CpGxo$f7bjcsOG`JLs zEb@`1hQ@OL)uFku*^x!QkscGe*d}^e=+AD)_Y(kv^8(q1i+Y3xgjWz!29}%ewBO0Nn^`3Q3KxtOj-?-XcCu&aO-~1{1|wc! z9}9YTPO@2%Ry}d*lHy#}17eKUOOaQl4Tr4UCB4NR&ohj|B$)|rf<4Kl8Iw!Gm|)K` zx-v-G=%~~zkvPPgmuf4Yv(jAz%a81$_oN-Ym|RO8FPNoKVvH5043iuLRW}Ut20)ql zTS+V`_2t7)5=(fD&INjt>D5C-`ErKH|N@&u{qg2m?xV_#C;@_NvHNK?-((1C1X87hMHkdg zB`m9=&B%gCn&?03?q}2v(-!Yt?u0^Lk?Q%AuB-%ytI>cDNGXok>Wc=|&2#p4NkGa+ z-}bfn`f!V4-YDe?<28Qgp79gOVK9>n0_KrmlD9DA$x&1wP4WgpKY+;t0Lu-U(^HUu zO`30lF^69BK=bU26tyiB!RhgD06I(h=#T!9fdN>MV+hAl%@H5UxFwKrBbGaFj2k5* zZ*s8O+(1pak>@O!X6V^~4)?}f;Cy>0tO=g3gJil&qTd()Yvf^1k(^Qk^z(>WU1|r{NQA z4ojJ+u!(0mw*<393RBLRSe}AuPM zIvVX^hv-+)xeLum$!RgX3oTb}YRdBE^7+wXA)g;NW^%+J*nG})WARgykS**i%QZD! zB8_Ol3dD-XN+DPc#QAk#Hk`X1T6KEjg&&3G=_yX;r^k8Y0GXuUYx`6Wo1BZUGBB-1 zsjYo{A;5-LTnyJ~b0FEx3@2QPU5s6Ay>;qdxE!mpy=V|C!z&C-=y||V*lG0CE^1Y2 z`+`{3s_bhv7Xrtu&A{|6_``($`yIXAivEdIM}C-D7iT9HJxfM!pGB@E@b?(_;Vt delta 1901 zcmZ`(TWAz#6#i#+W_D(0cGtaSFVSQZ6U_#ViSZU|g0V?Wqg^E1rb^Q;o6V|Qa^s&} z3cWzA;sq)V9HnWY)b^p6H$_^6Qi`>GC`hsR66T?lwl6+)%!Wd+(4I4EViY?t-+%sd z&VTvNf6jThb*I0J7g8{m>Fd~_S*?h$oFlF?yA&d8f_ zM4h%qr##Ym*67@%%cxu*F;>@Vj?TlqC)AV4i~QE zH3U(D7=eUfMY375lq(hU*<7i_i=*XI!OUiP9R8J#)eNG?Ikw>Oo_=w`A3U922uAKn z5qMYb5*p!a`Cz7w_K72;=VhLtNiD&8f(-->1mwUQ2{sZmuK=yCPz=s8OH!*I&2U5+ z$dj`r7RRc4r`*rnJBd!bp5R%hw32~<_A=cF$VAo89;}|z3J;Y7dp41ibAL{8eEOzs zq$634Es}0+|$?3^gW2NU`-`z_?5*Pl+O1qMc|)E1AOk;wj-cv zo(Eyn+EFGOHbW;n!Hm#MdU>%HC#Hg1eb^iC-6Gxp3B{DicY~z2cnq94Y(?0mLzQM! z5TOpqibgH$HX?OTkZp1(g>V_)LJ)*+!W)DxxM;)bZ!Xw7~AleS&Z+-xG{97UHZGoP39eO}B6q{+X!W-d?x8w0$o8UX| zS7JtmYrZ&K_H{zZ7YXRAPhxbbcuOy+{;i??xUHp(6-v3{_?iokQhZYie;x{nn z-#UprqfY*=gNd5Y4tHafEH5TmXh2wA^sweb!g9qeMSW&Fo-*4T@7p8Y+7;{Tmc9=* zBHkA3``yazhIqfY?)F{}%0H+I;*X--?}nq%eZs47IXbI5ZPAHM8??vbS;{M?z%6Nd z3Ij!{Z@H$+C7vV;r4OYmJvOIT=1wyvwobRiS}IRI)zB<;-^A2ddA!iWC*f*rr{NZo zv+*T{!%`UT$KEdbg;2%C#O=0-lHJ5a`CRN&?0oB$;hUkE*j=HU49eU{h2cP+J&wX| zB+uxn=v}S;nlcl;E9|z#06J!DhQn3#$ASF!IC{GU?!=GmZ4nw5{Lwkrh4*Y1%D?$) oX3Yx|wv4i^uwY`YY0msO^-G{>$&D&n-f4?u9ERaUgG9afKL~f9bpQYW diff --git a/backend/db_utils.py b/backend/db_utils.py index 03f3c2b..c5ba8ba 100644 --- a/backend/db_utils.py +++ b/backend/db_utils.py @@ -55,7 +55,7 @@ def list_recipes_db() -> List[Dict[str, Any]]: cur.execute( """ SELECT id, name, meal_type, time_minutes, - tags, ingredients, steps, image + tags, ingredients, steps, image, made_by FROM recipes ORDER BY id """ @@ -68,7 +68,7 @@ def list_recipes_db() -> List[Dict[str, Any]]: def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Dict[str, Any]]: """ עדכון מתכון קיים לפי id. - recipe_data: name, meal_type, time_minutes, tags, ingredients, steps, image + recipe_data: name, meal_type, time_minutes, tags, ingredients, steps, image, made_by """ conn = get_conn() try: @@ -82,9 +82,10 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di tags = %s, ingredients = %s, steps = %s, - image = %s + image = %s, + made_by = %s WHERE id = %s - RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image + RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image, made_by """, ( recipe_data["name"], @@ -94,6 +95,7 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di json.dumps(recipe_data.get("ingredients", [])), json.dumps(recipe_data.get("steps", [])), recipe_data.get("image"), + recipe_data.get("made_by"), recipe_id, ), ) @@ -131,9 +133,9 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]: with conn.cursor() as cur: cur.execute( """ - INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps, image) - VALUES (%s, %s, %s, %s, %s, %s, %s) - RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image + INSERT INTO recipes (name, meal_type, time_minutes, tags, ingredients, steps, image, made_by) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id, name, meal_type, time_minutes, tags, ingredients, steps, image, made_by """, ( recipe_data["name"], @@ -143,6 +145,7 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]: json.dumps(recipe_data.get("ingredients", [])), json.dumps(recipe_data.get("steps", [])), recipe_data.get("image"), + recipe_data.get("made_by"), ), ) row = cur.fetchone() @@ -160,7 +163,7 @@ def get_recipes_by_filters_db( try: query = """ SELECT id, name, meal_type, time_minutes, - tags, ingredients, steps + tags, ingredients, steps, image, made_by FROM recipes WHERE 1=1 """ diff --git a/backend/main.py b/backend/main.py index 6328b3c..74ac977 100644 --- a/backend/main.py +++ b/backend/main.py @@ -21,6 +21,7 @@ class RecipeBase(BaseModel): name: str meal_type: str # breakfast / lunch / dinner / snack time_minutes: int + made_by: Optional[str] = None # Person who created this recipe version tags: List[str] = [] ingredients: List[str] = [] steps: List[str] = [] @@ -63,6 +64,7 @@ def list_recipes(): name=r["name"], meal_type=r["meal_type"], time_minutes=r["time_minutes"], + made_by=r.get("made_by"), tags=r["tags"] or [], ingredients=r["ingredients"] or [], steps=r["steps"] or [], @@ -85,6 +87,7 @@ def create_recipe(recipe_in: RecipeCreate): name=row["name"], meal_type=row["meal_type"], time_minutes=row["time_minutes"], + made_by=row.get("made_by"), tags=row["tags"] or [], ingredients=row["ingredients"] or [], steps=row["steps"] or [], @@ -105,6 +108,7 @@ def update_recipe(recipe_id: int, recipe_in: RecipeUpdate): name=row["name"], meal_type=row["meal_type"], time_minutes=row["time_minutes"], + made_by=row.get("made_by"), tags=row["tags"] or [], ingredients=row["ingredients"] or [], steps=row["steps"] or [], @@ -137,6 +141,7 @@ def random_recipe( name=r["name"], meal_type=r["meal_type"], time_minutes=r["time_minutes"], + made_by=r.get("made_by"), tags=r["tags"] or [], ingredients=r["ingredients"] or [], steps=r["steps"] or [], diff --git a/backend/schema.sql b/backend/schema.sql index 905d5c4..aa7547c 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS recipes ( name TEXT NOT NULL, meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack time_minutes INTEGER NOT NULL, - + made_by TEXT, -- Person who created this recipe version tags JSONB NOT NULL DEFAULT '[]', -- ["מהיר", "בריא"] ingredients JSONB NOT NULL DEFAULT '[]', -- ["ביצה", "עגבניה", "מלח"] steps JSONB NOT NULL DEFAULT '[]', -- ["לחתוך", "לבשל", ...] @@ -18,8 +18,12 @@ CREATE INDEX IF NOT EXISTS idx_recipes_meal_type CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes ON recipes (time_minutes); +CREATE INDEX IF NOT EXISTS idx_recipes_made_by + ON recipes (made_by); + CREATE INDEX IF NOT EXISTS idx_recipes_tags_jsonb ON recipes USING GIN (tags); CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb ON recipes USING GIN (ingredients); + diff --git a/frontend/src/App.css b/frontend/src/App.css index 6af280a..8f706a8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -314,6 +314,13 @@ select { color: var(--text-muted); } +.recipe-made-by { + margin: 0.3rem 0 0; + font-size: 0.8rem; + color: var(--accent); + font-weight: 500; +} + .pill-row { display: flex; flex-wrap: wrap; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 10e6a3b..5d61159 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -19,6 +19,7 @@ function App() { const [filterMealType, setFilterMealType] = useState(""); const [filterMaxTime, setFilterMaxTime] = useState(""); const [filterTags, setFilterTags] = useState([]); + const [filterMadeBy, setFilterMadeBy] = useState(""); // Random recipe filters const [mealTypeFilter, setMealTypeFilter] = useState(""); @@ -95,6 +96,11 @@ function App() { } } + // Filter by made_by + if (filterMadeBy && (!recipe.made_by || recipe.made_by !== filterMadeBy)) { + return false; + } + return true; }); }; @@ -222,6 +228,8 @@ function App() { onMaxTimeChange={setFilterMaxTime} filterTags={filterTags} onTagsChange={setFilterTags} + filterMadeBy={filterMadeBy} + onMadeByChange={setFilterMadeBy} /> diff --git a/frontend/src/components/RecipeDetails.jsx b/frontend/src/components/RecipeDetails.jsx index 5c0d00e..402a58d 100644 --- a/frontend/src/components/RecipeDetails.jsx +++ b/frontend/src/components/RecipeDetails.jsx @@ -26,6 +26,9 @@ function RecipeDetails({ recipe, onEditClick, onDeleteClick, onShowDeleteModal }

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

+ {recipe.made_by && ( +

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

+ )}
⏱ {recipe.time_minutes} דק׳ diff --git a/frontend/src/components/RecipeFormDrawer.jsx b/frontend/src/components/RecipeFormDrawer.jsx index 618c447..98f32c0 100644 --- a/frontend/src/components/RecipeFormDrawer.jsx +++ b/frontend/src/components/RecipeFormDrawer.jsx @@ -4,6 +4,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { const [name, setName] = useState(""); const [mealType, setMealType] = useState("lunch"); const [timeMinutes, setTimeMinutes] = useState(15); + const [madeBy, setMadeBy] = useState(""); const [tags, setTags] = useState(""); const [image, setImage] = useState(""); @@ -18,6 +19,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { setName(editingRecipe.name || ""); setMealType(editingRecipe.meal_type || "lunch"); setTimeMinutes(editingRecipe.time_minutes || 15); + setMadeBy(editingRecipe.made_by || ""); setTags((editingRecipe.tags || []).join(", ")); setImage(editingRecipe.image || ""); setIngredients(editingRecipe.ingredients || [""]); @@ -26,6 +28,7 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { setName(""); setMealType("lunch"); setTimeMinutes(15); + setMadeBy(""); setTags(""); setImage(""); setIngredients([""]); @@ -94,6 +97,10 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) { steps: cleanSteps, }; + if (madeBy.trim()) { + payload.made_by = madeBy.trim(); + } + if (image) { payload.image = image; } @@ -145,6 +152,15 @@ function RecipeFormDrawer({ open, onClose, onSubmit, editingRecipe = null }) {
+
+ + setMadeBy(e.target.value)} + placeholder="שם האדם שיצר את הגרסה הזו..." + /> +
+
diff --git a/frontend/src/components/RecipeSearchList.jsx b/frontend/src/components/RecipeSearchList.jsx index 55ca5f2..5c60610 100644 --- a/frontend/src/components/RecipeSearchList.jsx +++ b/frontend/src/components/RecipeSearchList.jsx @@ -14,6 +14,8 @@ function RecipeSearchList({ onMaxTimeChange, filterTags, onTagsChange, + filterMadeBy, + onMadeByChange, }) { const [expandFilters, setExpandFilters] = useState(false); @@ -25,6 +27,9 @@ 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 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 sliderMax = maxTimeInRecipes > 0 ? maxTimeInRecipes + 10 : 70; @@ -42,9 +47,10 @@ function RecipeSearchList({ onMealTypeChange(""); onMaxTimeChange(""); onTagsChange([]); + onMadeByChange(""); }; - const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0; + const hasActiveFilters = searchQuery || filterMealType || filterMaxTime || filterTags.length > 0 || filterMadeBy; return (
@@ -152,6 +158,30 @@ function RecipeSearchList({
)} + + {/* Made By Filter */} + {allMadeBy.length > 0 && ( +
+ +
+ + {allMadeBy.map((person) => ( + + ))} +
+
+ )} {/* Clear All Button */} diff --git a/my-recipes-chart/templates/db-schema-configmap.yaml b/my-recipes-chart/templates/db-schema-configmap.yaml new file mode 100644 index 0000000..9b901c9 --- /dev/null +++ b/my-recipes-chart/templates/db-schema-configmap.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-schema + namespace: {{ .Values.global.namespace }} +data: + schema.sql: | + -- Create recipes table + CREATE TABLE IF NOT EXISTS recipes ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + meal_type TEXT NOT NULL, + time_minutes INTEGER NOT NULL, + made_by TEXT, + tags JSONB NOT NULL DEFAULT '[]', + ingredients JSONB NOT NULL DEFAULT '[]', + steps JSONB NOT NULL DEFAULT '[]', + image TEXT + ); + + CREATE INDEX IF NOT EXISTS idx_recipes_meal_type + ON recipes (meal_type); + + CREATE INDEX IF NOT EXISTS idx_recipes_time_minutes + ON recipes (time_minutes); + + CREATE INDEX IF NOT EXISTS idx_recipes_made_by + ON recipes (made_by); + + CREATE INDEX IF NOT EXISTS idx_recipes_tags_jsonb + ON recipes USING GIN (tags); + + CREATE INDEX IF NOT EXISTS idx_recipes_ingredients_jsonb + ON recipes USING GIN (ingredients); +