From 5f6645f22a160e516d5196c62fd53cadeca4ec56 Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:54:37 +0200 Subject: [PATCH 01/11] Update env for aws --- aws/.env | 13 +- .../__pycache__/email_utils.cpython-312.pyc | Bin 4101 -> 7794 bytes .../grocery_db_utils.cpython-312.pyc | Bin 12347 -> 13344 bytes backend/__pycache__/main.cpython-312.pyc | Bin 37720 -> 45991 bytes .../__pycache__/user_db_utils.cpython-312.pyc | Bin 5098 -> 5863 bytes backend/main.py | 2 +- backend/schema.sql | 154 +++++++++++++++++- 7 files changed, 158 insertions(+), 11 deletions(-) diff --git a/aws/.env b/aws/.env index 233b372..36f836f 100644 --- a/aws/.env +++ b/aws/.env @@ -2,7 +2,7 @@ DB_USER=recipes_user DB_PASSWORD=recipes_password DB_NAME=recipes_db -DB_HOST=my-recipes-rds.chw4omcqsuqv7.eu-central-1.rds.amazonaws.com +DB_HOST=my-recipes-rds.chw4omcguqv7.eu-central-1.rds.amazonaws.com DB_PORT=5432 # Email Configuration @@ -24,11 +24,12 @@ AZURE_CLIENT_SECRET=Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp AZURE_TENANT_ID=consumers AZURE_REDIRECT_URI=http://localhost:8000/auth/azure/callback -# Cloudflare R2 Backup Configuration -R2_ENDPOINT=https://d4704b8c40b2f95b2c7bf7ee4ecc52f8.r2.cloudflarestorage.com -R2_ACCESS_KEY=1997b1e48a337c0dbe1f7552a08631b5 -R2_SECRET_KEY=369694e39fedfedb254158c147171f5760de84fa2346d5d5d5a961f1f517dbc6 -R2_BUCKET_NAME=recipes-backups +# AWS S3 Backup Configuration +S3_ENDPOINT=https://s3.eu-central-1.amazonaws.com +S3_ACCESS_KEY=1997b1e48a337c0dbe1f7552a08631b5 +S3_SECRET_KEY=369694e39fedfedb254158c147171f5760de84fa2346d5d5d5a961f1f517dbc6 +S3_BUCKET_NAME=recipes-backups +S3_REGION=eu-central-1 # Automatic Backup Schedule # Options: test (every 1 minute), daily, weekly, disabled diff --git a/backend/__pycache__/email_utils.cpython-312.pyc b/backend/__pycache__/email_utils.cpython-312.pyc index 6524825bc88ccabe7c01941e02c83613067ff80c..0f2969ffa10a832ee52eb1053722030c585242c2 100644 GIT binary patch delta 2794 zcmb7GO-vkD67C*mdj7iS2L=rIhZh{j49gfB7H2aIgxFT>M2_=gD^iHG)-bOJdKsqC z>mKYu?H!_cca_Tu)Sy6YBAg!?;~Wxk@gY$XDThdrB1j^&g>vvA+SO_gJ6^mOYY$u1 zJ>voAv@P}bs_IqMtE#T|UjK>wI~Dp)k^&q&|N8Pp<*U}YP&5Cxxx<-~!VQa5xXKMn zw2^w{5cR?9mqS^B2CjOB!!#&IP?mL5@v3_`TIH!E*Mm+7WDT}m80;dpT@+-ostoMv zfp4TZFWG(?fNw%R>!SV`Fnfx8yy>&=f6w#b^@Qsa7yr=Qa-ZTKuD5xzZvK|}Xa6z4 zJN{+9-F!W8!jptF8rGKs|K&mao_HeI4+0iN7&8AZc6v?%`RaO;6yw{5s#6!@b?gK( z9)#xrK+tc#5c-YhG%#klXa3)^5bMmNfUV@t>jn?Y*`~h6T%*ZW`q`mR)oC()6%oH`9-^M18g72 z3JCyf)>^VYvKFix#JU0G4eNbto+Jnir8VmsOss%(nOL{1>(&z6j}8wYw4sMP009Dz zJ-uKpciEgHx(SAJV6|c`5Fl=X&O8Br$BOei*0f!`cckW^1iJ599~0{X>pHRK!NC&f zELf|^%vrMp!ZWQU$K^+uz^t`~bG1A=tTizE5L6PqM+s=(1pBpk(q5#nP>tS+cNWux z@G#gV&=hZ>JB$~cn*smvsG~Or^Q*A-d{@ByTsSNgu0RU~%=^M#GvXZ%rN(*&YdM;R zx22St9JHhV|ErPeo*cAS#Ui7d-6gpOp{+S3xei}!5exk5qLg-5CAr%vRi=oh7jyDJ z`$U?JD)}VoJvw=r9PKB%d|6MVbIK^lGBWHm*56(W2LeiyF(zjR+6#Y$zHcXwBE@K{ zPDj$E%=jo%3wfHzs5zA-$-$nUOkbbePb&PSF+~S%GEJ$HA5D^DJ+M9o(f^oB+b?nA zqH;!MXwJ6iWqlkISo>Q2|nlj?@b+N^oom+ITyz>nH8rRi{njSlW; z&s29sb|hd9`5VogZ^-=A_q6#>U$dv$i^Dp(m9 zindxbJejM<*G0#db84N1`8?Vt%nt(NZTDhic5r3?YGHMBBYJ%1#Rmcx-naPDkD<<) z(|23WnEQgQ$J|21Qx-WPGIO@f2~kg35=Gz4sq#K=BvuY_jcv23G8ZA9a(EBQAM}{h z!I4PdRE+;d&Hn^1mN>&j>#nDVT~0ZNfIb&@*X}Kb?zb8r%O58{shreuqii^j=04L1;z^#aQ~7Yo)I)&dOLAr^I2T@b87;-xcdZI0>-j9#PdC!(SwZo6GJJyRvo!Ohg$r zhuvQfeW?kR7IN|+djn*cgZ7>IPPkLJ+uXXrHJa<;GjDEl^?an>&OJD3C@c delta 327 zcmexl)2hICnwOW00SNw@+hr#4PUMqdTr^QV*(HUog&~S3g+GNol{bY0h&fYv)7VnD zT3Dm_Qn*w3L1JkvDLgGKQ35c23U8_)Pz@hYOsJAkQ(&_YV;mzR-{wN5HB5|EljT@l z7^^lXv!<~yYEC}IX$U0$aL#2^m^_QiiPa3~1fk6jx&AN$h41k=ahn4LiUdJ~^<-^c zEmk`qGhlNRuLz^OHB2f*lex$i#9;*yHb4R>U_QB*Pn*>V$cdP|moHgVld(t+BqLm; z22us`Y?1clcQQVzV6FThJ|~C(IiW}cM1btN#gdetpHt*Cxk6T!%N;1d2*kxDljqB7 TO+GK{!S#hfh*6-37bpk-zAZ*O diff --git a/backend/__pycache__/grocery_db_utils.cpython-312.pyc b/backend/__pycache__/grocery_db_utils.cpython-312.pyc index 6a656035a86706ea5b01b95e0a80c585fbb771be..75e7239c8e1ec05a66618ab396c94ff3a26e45a3 100644 GIT binary patch delta 742 zcmZ8eO-xfk5bm~p?c?>m{y=|#{;;4>8Yvzm(TgG^7{y8qh(s=J<8EywZOOh86niop zK&#N~fs+P2X}pj;qec%Tnivu zD3S@}%f@hyc2=Fu=y5fA9JN-nc@(U^?MET*N72J>DiIHL6Ome|l{%=9F}FaLp&5}h z&08m%hzv0js7T!z8}WkA%M&Q5_yGAxnE0uK1o#LF*lnbFUYPV(*V`Ie;4yF&#Uk1> zbh{Ou=BGkkow&&G%rr@tD5h6(YAIK^gw<@CQ9MmqKBua=VnNl4p(%tO@DJe=(g^f% z15~}v(~WI>I5MkP;JPa|z9hDDIe1bqmrZ5FF9Z|evcMcAWjCTMJVZOVfQY4m{|*lD zAHg$y*14YXzm1SwX-|2afgaO^|N3KHK*ARebnv?&dsxDIWNhdhzL+i~sljs;ck*=9 z%SUmH-|{H@m(9sn!pHhYST_)`1RSajj-7ng2Jn4gXCle^Ad>$E!Op_#nvt<+e16C! z__jsIl4H?PGYg8lCYzl86G zuHEKM#k*fxr4+#Lpz53YYFgr-P&5A-pK3LBR_~n|l+55@MjlJ?x!$mp0cfm5 zeWk7Sx=>#e@~w{5Okkd>b+bw8MxRLPDWcP?emSqx{8p;4HBtO(#MOHE`oWuj0REQB AkpKVy delta 284 zcmZ3Gu{(k9G%qg~0}zO^*kyVsP2`hc?3<|Wz$iH}BbQNX;x&6l>B(Y@>gHUjTqzu> z0%>e1!YwRO!YMppK2s`7Dt9Vt8j~c@1Q1SRNf80ci=@b;h^BIl2KE3^CQMSDMq!)g)(ZJ_sC>3GHOoNkaJ`7ot!T>e{-%9D-)ML(6EI- zTwFStkwawj1eGn!TtPtoCXiSoP)vSvu7(~nWANlT+7?{lK+%&RnQ1^7<;{#bvsqYU zf&BZM7aFiIGKx=LW|%GN3lt~Y($2q6Rrfx(vI zY`|#@*w`)F*nrXO;HTMM_jLCRKGyd3;T8zD;zveCMrKAtX2wqjUsYZGH#o*9lAo|%rBo>`7rp4pDso;i*=yv*D**Hh*w^UQP1 z^OQTvJ@Xy&JqsKQc$uZC!n4q^&{OHCRnl)8dbQ-PYMpw@6|I#4qd)LKrp0X0)0 zcO@GlslipeEC$gfj`2bHdnhfd>IRYP ziq%Ji8{tMBVzhiow0xkQLR=eI2(ZTHgd)-lWN48o?3P$ z&X%=OS|P5I+&eYm>Lc1{tGj@A0C}}*q?(@P%xbXZt(BU@l~OI|)`{yB+#cZmz057H z@7aLdS;#HR-KgO7Mzr0w3FSuhBqD)vHHbvq+~bH)cdErLl3HrPK8afaYvR~8z*I67 z-*@W7?TQxmM0#1|l-6J`BR(bWkXG(giLN7>XfJ7mx3ErHt!@wg-2LApwUojpX5s7j8q|H&wZId>J$F5z$dmtiL`-HSvo~f)r)D}Aw zoI&7}Z9K}EhnxqbO<@}kD!4m=Tek5hqBb6r+o|9@C^^p%_VLtI?ZHtrIHce_9O;+# zu(UPoWhnEAEnFYHcv4a0SVW(^Q=rc_S)YjSa&LZ0d|Eu+a|Yr}dX7pwE475@>luZV zu85Qz22!?$I=#@n*aNyozyo>NFT5z(BS=RS2I+&_(YQNeXG%Jsa$H(yY2 zoQjMWximt~iwdr%fQ$P8c}XGXM+%OoBRwMB3XaPPj?+;YKSqw?m!l(dMZtAOIxD`i zd%Pgf3>eL{Gi29TjIp*?#jD~qv9jm-4UN1=5TT8b~SZyBE?M8m4hGjc+<))vtP z0jbp|QKzfb-{@?i-o0*7qQyoQG=G#4N)L1R8eI?wTO{gn`+ROM+h{o$)P^iT*y*Jn zXPv8lcWX;1A@WWopWjQRP(qX2=XcV_!VJDpLWAUYO3i!S)Z6Tln*AX&5|E*_ez)W= zR)x}hei!wJD>!}ijgr{fB+*bRNNiD(wVd8NV0n$?RX`^x_LMF}fcqv|iC__e#jM*} z6F(181S{EBR+p}Zzp~4jlh}#G#F-0jV>8ka zh={B~z!l!yyC~VIBDL&F>JqY-eU|#Fxef|_xM{K5X)WtcTSWG-Kc-cP$X{%tBlPC# zalI2zs1pHKeg|S!03l<2lgsDxNd88zNSl#7qB~oVhQQ5Eq`T$rY+>CQ3)$QZqhl+U zZU<0%UHjDOcq+U%V*XQtiN*yXP72isaQs$CsG0SL|@ z60@rVI5?~{>p<>kvL71v06 zS|LzSK5oU|vjlE^+zv;~CH9OWcpm=3e7IumI4zbjch8U^?P?q&GazT zsimX!;~11s1TVo~#7>Ns!^^-}?rKuy!8;ZnicFTDJ!yG4aE7#QzvS`N7RNo>o=>7^ z9-2n<=L8lYxWX=E=gMPuf;aJD_Qo|Ro?_i%!${e@h`VOYXCK+hQz!?)b#@?UlHAHu z>}Jl-ins+wn+fT?`8fggr8_UhxB5QS;_x(cN>P1-X2gj7ls^bOyyR;XOFy@(d-e;kRrGRe-V4MBps=ht>t;vGNzoJvaccQ*}vGw zZ0E58V*@vQ-df1O7g*8h@{iKp5raHhcQ2th1n)3s{^N3YFY#gQ&0ks@3sKQ#Ym0Rw zt!YFvBL?&`wt(QbY}pvQ+`?t(*(n{DzJaL_2gwNMesYe zzhI`^&dcn-7yK`kJ-fGatcKV_aeRx=+ENc((Fd2e!|?Zoovq%M9J~=Oz9xU;bP8znnLV6VsJch}qHor^uX&#>=nl$vFpmKHm22)vrm$3fl*+YOn+dN`%M zu$7^201F{t0Tw}+E&Olc>rUaJPS9q3sLB533+(S9lZAxzzQ$I+2rl;jsr}Tou>awT z{S}o#yw8pepxQG=QQH#HAguS#fhR7kx2(1W_0IfIll2Y#DHQe|DcnxLcqgZ4kcG^Z zlWgW28uae#MyjAEMd`(AnutxWV~(Oi8}6Y(CV4L?)w^3HAAJW%X2LEMy+?l0yJP%i zk~bguxqw(aT7a|)1ialk7oz_NpjaEyi0*nnuBrPZN|Uevw@>sImTKsGh`$d2_*{Nh zNKL)_d^ibqcqmuMTu&w3{Zg(H{QwFJ2s=07eNxlAeWFvH{4Q44iIU&tZjv=?V||ks zWO7X(siaO;l2w*Y4BkQs+WICh>=S+oIQ~u z(#@jOB*n(~4^ZsvwM^eiQm-Q?j$}yhaqV~d-5!Zn_68sSg-U5@)~H4CX{UEs+pINp zsC#idO-2zoj$Eyf7Pb=Z7Cywd>GHMK!3ID+2B7XRpsr@o>j~-V8@=v&3E~u=$bUrv zl?dKM@D_r%S;_1I*g~(Kovi(LAhh<9Uo`A+RCfdm%qy5C%SDU5g9yP|67Bhq%$;Zl*d= z|6kcJ7hf!@0iJe!DelcnJyJc;qfd0&zXR_eOKaFb)gktIRk|%fv_^6nJ&6hvNMa4u zvvu5b@32>^^MkzS(VqTSOn^Rs=3!6#5Pqluw2`MZHZy7--Q=RrfXRo384V0NJXV88 z8KLH12*f?#lWf6~JupR?=Nr#9UI^Zd>rb0CY>Ixdgpo`=wj?W#4aegvSAHAYwXBM~ z!d_ptXzmD|W+LZo1hW8y42$>IOD%r4x0%lb^nr&5-iOcFl;!E*S4)GP{E6~W7Y|W~Y;0QhDpb7^OaML)17&r05h@~Nj zhBOEQUXRD^rwO1%NKd^@O}MPewuA8vLUS^9UZFcsLy67OK4*)3eqsN)DsE;h>LmjE zC?PtbzR}eTY4va=k=lT7a&Ot{MwMpPlqpl#wY7z0F8g5Zn8&cekcmp78{)a&*-G8f zLp!!@YVdj+nk1)exnqEN3wvsN2Ai|a86OV}*v&8Qvj5!vQ?_;ed+8GcdeMvy(P040 z!m;>(o_*p>ElG$bU9G!e`shhN+Bkm#hL}+kq4_{S&2CLfVPEaYB8jYNLxCZ=9e6uT z{zNw4m8CUwm;+|^dQFN&Oo<5Xu=vy2L02lVv7rqmi&I5wPg=w~I;;U}L@hu1EPwnM zfcVps9+i^l&x=s~`E19=RF++rWGsl#Tk0li&lL#%@$6(>4#{WN>t?gL zn~KTjOxjdzl`R-5QUOMdma~^O%~M2V9(Qx0_%*2-n4H)mUKAsP$edF&^fD}p?vWkYS6g8*lnTW)cxY^7a52pM=9&)p0eIUe{8 zVS90lv>YoGVO99XDbO$Rm3uk%Eo829!M6*n-R$y6p}2Z)Yct08$ALGL0IMe3?=VmN z8Y_K+K#MXkriM&AA&=*mnnf_yCdxe^73l1BM@mU}i;@u50x}=6i_L@Dbuh}lEC76$ zJfvI7?mPZIziY{mPO#<-B-n2!*t_%k6QuJUKF^s$6)LRL#{~ttG^+)!PpelN~eZCAf%*CJl1t{&ezZkv^CoFXp*uJ%?cl}6OAg*X$P48c}|B7&5O^Eucb(pUvMjS#+8pT(W zA`6hhSFMxdlf;y;koXRxn9AK1oi|nCyy=D%ku)hBI4#oB2nS9VlX^0SIdE9lQ61P8 zmGV6Y&dI!_3MBSqMRe{+@{e%m`PpJlv^3dY1mnyfF;~nBB)Q@D(m9Ky*M1-ImT^3&foN zQrw_3k)KBly==69EnrYQV^FyL5N>NOVxxoyYAM5#VPQdy8E7JP2{l<+4I7ts)$P!+)= zvg%cvp_Puf198x=xQ-wo`^Y|Fzk0KH~%?^|^K)V7jm+b?W0lf^BpOqJJJbrzNrwSllA9#(! zi`eLO5PA*ZO9SWZ0}RmT?bUoghHad)MAx_lS^^fH86MTy@BX12eLSk5epew5P2K=hVZ)2C>W=q;u@WW||8P$tR9mh{@3b zY&B%$Lc*uQr9h!@0SV562>3Pw_r~-Ktc3fI$c`8f8}in`s8Ex}1Vo%ZIO=v-_N!=r zXbm)C1jF?G5#)xq13B7u@+oWq(y!)R&goAm?b3Xbko<8^F|R7I(*3I*7%=Awfu~PysYosDfYU1Vj2@TFyY)#M@~TuPy9PEA29V1;h>H zBmJ1}nOEmso_BNNU41#EyD~D*x1DY4&h4AJq(6OWm+2!*(Y3h0HD5R1eB#!Me#`0s z{pvpb>W{6qzO4Oy32lA)wjr%xSVO*fs25DB4+J%;tT*>r$6fJVoBHzJ?#8~LrSz_T z%3!3*hx)ev7>Yx_?|f*>he)NB9LZdmC%lB%kJ17A31a`FA_vg7Qpmy_&0DE)h-V}H ztsH`Qo}tpLertSYrC$BEiU9t$S_`Fb>$QlR)quArl)}_3lpYbj^w2sFUvPsg8on?b zGzlKkK|HDs6%RNhz)o*VWx+CoS`Ehl`}%AazaceW6VUj~egg>Mj|j1O&D__+TjrJ2cjKx_K*-e zfdD19L$BzcS+vAXB5;5*ZL$RuI`jwhBZ8Z?y>JbWWzo>q>T@?Y*tr|qVW;eo!yDfc zfYT@Km8g4X+c0NOhWwwe74AwYT?;oat(ZiI$cF*%`oOjyh+`O!E<&(^F!dU+zbwZm zsB~rE86=$pBE&v<_rnkPe8+-;kHR1c?bF%6ubhxBN8)+*LLU#mG!=sZKZ2(QM+={G z?6t*Kx*WMyB3Ok0&n1eD$}N0-)RCN~VGTTc^P`O%a}4tNCMFU0DM89(5Z`h96Y}3f zz_%G_m5>CHA2~;Ak%uxeDReb7GZ_QUL_op;r%)@Af6-Up*9nOQa$M-QPVLful#p^S zDeqb(#FN=oDo8gZS7U4mnyXb1GOYRCsr?B>U7C*)k~v%7tTlI&*7lj!juM`tayD~0 z4j!W%D8@FfyJ2A5oZI8(^k>ZNGT%!`KiS@;8MGvw-hF)cNl%x0P;WeKJZ>B`EgUpw zURc~`S~KRMR&9tM(hCMNmYgypi{ZH8lyOjRIvsyJ{*(#2V#vNw{iB+THFxzBc-*S& zOIXyWU-b2-#*7C7(OL%e=6l8zL?y!JugROHM(jIZF^u6MvS=(hQaPz|s_^y{#NVEp zv?yQwj@q^;Tm4S97V&)bqOmN{@P}M}l6)5D;2eNr70pMSuNjb~Vu8ktJf3h*;$Hw7 zsJC9CZBBfF^dC&Z7B%Z_+zeTFv)e{Cp)~k>a0X@YbL&aOTnIJ-SjRt@$=?!VZEC3< z8Djjm@HMQ$251BHIIt^iEY~AFocPCFj#3-C85nn}*&Dl#^Y2dW8SK~o3>AHmxpu#8 z3Wl2=1w#73=j_AVS(T)@vv7e*)AI z`PXdxFu-2-+O#5Yyuz|+J#@Jf!b~J@`hsu8;t}Jkp~cuJA79=k?})Ow9oU=;fTB4z z$v;lJ6KFqS8~t8r?3);LK@r?r3Y#(i za0^}LLv#ge;ck`kkDPoMUc%Q{4vA0QkAN0Q@{eFb>GF0h%2{kwOl8h0WVnFfAp)Mx zFGP&})Vw+YBu4^v-G9 zLS!$<124$!{b~DZlJ{skfEHV;*q;yNvt@x|Ik)!LJ4SawQ;=AR*-z1M^|d1`DWv1W z?W6dC%cliq9_i&kZ;!eYdl1Ob?g5r>SVG4GZcz2Ge{rXVX!0AcZP_NqQH! zkCF4kPLBb#NbtzTP9Ml(s}5M;y56eR60LBY1XS$IwiHw3P}iY_limvlvKPj7D*+*L zD(lesjgj`c$W9^}j_3orMbTU1Vtiyf)d3qV6f;GU1&$Mqs6qf6jT22AkCPL(@XoWz zvNGfS36WNyfG37x31pfPq3ueH?9+nEPbIP4UxYEDzLKjrsl7nHs$u8zZ;xI?Z1&mV zKw)>;xhem!c|Hbu!nuv9gIP8_&7B)~4bPHzsDl&Sz-!b8bHQ>`(}L51^Bj>6_5ooV zbg+X2h$!GF99Q9WOCLF93Xtc>c|wF!iJIso1B%{W%P$JR8rA?WA>!k)AsqTUxvf$+ zl(bJ9{c>e<5N=3{EnfJlH@S>o8S|A-E^CmQrTs0md?A_^CdR+!dzU-8o4QxutmvDu z{Py$}eXBP1Pv3ZH&+Xhz-|$Znhs_z}Y+MGZjeTa5SJG`DfMOKFD?VNrLbwefdt*Eu z6U0IuWG5iT;|e-vNCPVPd2|WIHM)E*zn{|mFlfVK4WEP&0mm%&RCMy`qb*P;l*Dl- zp7ovbH+G-=`DDie?$qJ@vHcN)6dfbl0n#S%Q32#@`0Sm;{vkLu*m&3C?$f(JO__MD z`sT(rb`6xTzFoe$KV?m4%}2(JdzP$g>b~iCfGC zZQR}5O?{5-{WfQxChZer&hQGK8nXtCITys+#)3g>`e1g!AZ#3$-A*hWOf9-*xt%(5 zFld4E>upQ%U}Dxl;@I1XWA9Zg>07#`Z)?NtrHy^NTJKiu?MoWlz2$b&)PbbAx0B}f zC(Vm}k9%kZlUsnB#0u3~{8^`Rsvy)?sCKB}MBYJMsu(lBX4GyjMIZ2$`Xm>vZ=9^6 z6Ie;-JaV1w?!2S;*x|w{#p#DNE0(m3QB@SoTfXT796=aTzHo%JTsq%*E7n%Did4Bp zzMA-A({c!^uEl9t2g&cBA3RQ6+%kBZSFnlLI(Zdep9KKln|V3sd?b^aPQnT=BM9#R z4s$)(tB3Kc{VxyOb;p3<*7Ob?9vj|Jyuvb%7T8Ll4t!khige4T0s_AkyWwb|?j(>8 zXxPD{MX>j|el#d=MP6Z(pB$e&85rS04F-RV69VQj-A_)?Jq0ga8g}u?La6hPPad4k zb;rK7r__4=?wxM@+T(4u%QqoJkWmVS^aRw4weCMQMt2%0XEp57v7%I5FJMCXwb4@f zzE3@T{dA|O4hih?_)NBcn+Xr$Y8G^61pT-K@#jYPmxn7IhAW@d4`&_u5#u0;>5)3@ zybdSgVnch$I_hp{kT8~dT+OYnCOh7n!7oFRJ5uAhI(Bb!Q(H0r>?L0s`8JqZf!AB@ zuKIe(=Ys^)o>usPDe_$++|_F3WH>s4sc11TMIkcY0wv@SGX*d>GVn#!r5qEWhb(ZT z1CXB683P_;)@@_fg=N>Yca4+7Zo2%gWksKU#i!=1esfM| z)u1u)Oz|CKHZQ$d^~TKCSKhVYVd!IHVyAp}xZN^rxP9MC_~I~rF*wTbL+W>w3Gib8 zc}5QHKRzcS+3?6ZmO}KahsV>d+`BPq8{{qN}9#Mi5OHovL#gJhxN(!NfSF#AV<5Fk^AKph7FWsWns-tKYPwRg6`? zIaUieSWUX@rI2Od|Kvfx)7>QDcT{$nro|tDs^=m9zl-qveA_l-)NR=})Wj}!nu6kJ zj;b2PQB~YgCqj}5TZ;K7Pc6Crikrr!l|F+bkN!v*fZ#B9oC*p9;dv~L`6SxDJ#y(R zA>x$bF4IfE{3cG(GGK!#GND&0r)c;LeQM6Wu&LiXF6@?BKj4?_onvKcM*oeeyRMRK zcB9KN;UTmk-&e{(EEfT8heFoKi7ax{iIpBNiIY!^6WHeCS>iTdSI-R@?^ZB_bnta}T=J19WY;)OeOzr^Zi-`^`G$-u0rBvKKciwpi@)48^^DK@rzXawh_g9J=DfG4r36-@BOp_0ea-o>^pqO`{m<)g$Z7zR(BgG>-#ltAYqa4L^72oSo+*!yM%yE^cxN1{ehAFO~ ze2t(N`A1DOOj?Dit7&WVi0ixG>dQP{{5M49^bO#H{{`QN23B&ie3q8LCJVq<783v= z0`7AxTSru13IM*c#xQ{_CtqpU(!z2uZoH^da00PI;(T zk+cg?3O^4`EcMhBC`^_MlOO8Y;#1GfO%YVuW7!9@yWIUGqvJIXQ(5{kk1+F$j}ZI>XmUY zqc1+YAJkOkk#^EoFzbPU2(sqNw4p_imJf4g^@Gl;A|jE#F*6nh;Gnq z9a6*Z$MCC#U&zNMjvIgt zN#-Xudsw>KNRkGV(jMS1O2`^g0XCG4+_I{}*%L__XNTV~J77cOqhpZSPqH9Y8`WNG z0{NE2p>j1KA45Y(z?v{pfDPpe8dF#M9kp#xlh9dxZ26((XX3kUH&fmaf2XM&QVW_) Hei-L=R{Xo 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 Xer00|&c_^h9xL$Ql1YZW1crYB4ZM`N diff --git a/backend/main.py b/backend/main.py index 2a792fb..f520815 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1231,4 +1231,4 @@ def trigger_restore( if __name__ == "__main__": - uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file + uvicorn.run("main:app", host="0.0.0.0", port=8001, reload=True) \ No newline at end of file diff --git a/backend/schema.sql b/backend/schema.sql index 5b61e17..a432695 100644 --- a/backend/schema.sql +++ b/backend/schema.sql @@ -8,11 +8,13 @@ CREATE TABLE IF NOT EXISTS users ( last_name TEXT, display_name TEXT UNIQUE NOT NULL, is_admin BOOLEAN DEFAULT FALSE, + auth_provider VARCHAR(50) DEFAULT 'local' NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_users_username ON users (username); CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); +CREATE INDEX IF NOT EXISTS idx_users_auth_provider ON users (auth_provider); -- Create recipes table CREATE TABLE IF NOT EXISTS recipes ( @@ -20,13 +22,15 @@ CREATE TABLE IF NOT EXISTS recipes ( name TEXT NOT NULL, meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack time_minutes INTEGER NOT NULL, - tags TEXT[] NOT NULL DEFAULT '{}', -- {"מהיר", "בריא"} - ingredients TEXT[] NOT NULL DEFAULT '{}', -- {"ביצה", "עגבניה", "מלח"} - steps TEXT[] NOT NULL DEFAULT '{}', -- {"לחתוך", "לבשל", ...} + tags JSONB NOT NULL DEFAULT '[]', -- ["מהיר", "בריא"] + ingredients JSONB NOT NULL DEFAULT '[]', -- ["ביצה", "עגבניה", "מלח"] + steps JSONB NOT NULL DEFAULT '[]', -- ["לחתוך", "לבשל", ...] image TEXT, -- Base64-encoded image or image URL made_by TEXT, -- Person who created this recipe version user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Recipe owner - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + visibility VARCHAR(20) DEFAULT 'public', -- public / private / friends / groups + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT recipes_visibility_check CHECK (visibility IN ('public', 'private', 'friends', 'groups')) ); -- Optional: index for filters @@ -81,6 +85,148 @@ CREATE TABLE IF NOT EXISTS notifications ( CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); +-- Create friend requests table +CREATE TABLE IF NOT EXISTS friend_requests ( + id SERIAL PRIMARY KEY, + sender_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + receiver_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + status VARCHAR(20) DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT friend_requests_check CHECK (sender_id <> receiver_id), + CONSTRAINT friend_requests_status_check CHECK (status IN ('pending', 'accepted', 'rejected')), + UNIQUE(sender_id, receiver_id) +); + +CREATE INDEX IF NOT EXISTS idx_friend_requests_sender_id ON friend_requests (sender_id); +CREATE INDEX IF NOT EXISTS idx_friend_requests_receiver_id ON friend_requests (receiver_id); +CREATE INDEX IF NOT EXISTS idx_friend_requests_status ON friend_requests (status); + +-- Create friendships table +CREATE TABLE IF NOT EXISTS friendships ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + friend_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT friendships_check CHECK (user_id <> friend_id), + UNIQUE(user_id, friend_id) +); + +CREATE INDEX IF NOT EXISTS idx_friendships_user_id ON friendships (user_id); +CREATE INDEX IF NOT EXISTS idx_friendships_friend_id ON friendships (friend_id); + +-- Create groups table +CREATE TABLE IF NOT EXISTS groups ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + description TEXT, + created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_private BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_groups_created_by ON groups (created_by); + +-- Create group members table +CREATE TABLE IF NOT EXISTS group_members ( + id SERIAL PRIMARY KEY, + group_id INTEGER NOT NULL REFERENCES groups(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role VARCHAR(20) DEFAULT 'member', + joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT group_members_role_check CHECK (role IN ('admin', 'moderator', 'member')), + UNIQUE(group_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_group_members_group_id ON group_members (group_id); +CREATE INDEX IF NOT EXISTS idx_group_members_user_id ON group_members (user_id); + +-- Create conversations table +CREATE TABLE IF NOT EXISTS conversations ( + id SERIAL PRIMARY KEY, + name VARCHAR(100), + is_group BOOLEAN DEFAULT FALSE, + created_by INTEGER REFERENCES users(id) ON DELETE SET NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_conversations_created_by ON conversations (created_by); + +-- Create conversation members table +CREATE TABLE IF NOT EXISTS conversation_members ( + id SERIAL PRIMARY KEY, + conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_read_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(conversation_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_conversation_members_conversation_id ON conversation_members (conversation_id); +CREATE INDEX IF NOT EXISTS idx_conversation_members_user_id ON conversation_members (user_id); + +-- Create messages table +CREATE TABLE IF NOT EXISTS messages ( + id SERIAL PRIMARY KEY, + conversation_id INTEGER NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, + sender_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + edited_at TIMESTAMP, + is_deleted BOOLEAN DEFAULT FALSE +); + +CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages (conversation_id); +CREATE INDEX IF NOT EXISTS idx_messages_sender_id ON messages (sender_id); +CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages (created_at); + +-- Create recipe shares table +CREATE TABLE IF NOT EXISTS recipe_shares ( + id SERIAL PRIMARY KEY, + recipe_id INTEGER NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, + group_id INTEGER NOT NULL REFERENCES groups(id) ON DELETE CASCADE, + shared_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(recipe_id, group_id) +); + +CREATE INDEX IF NOT EXISTS idx_recipe_shares_recipe_id ON recipe_shares (recipe_id); +CREATE INDEX IF NOT EXISTS idx_recipe_shares_group_id ON recipe_shares (group_id); +CREATE INDEX IF NOT EXISTS idx_recipe_shares_shared_by ON recipe_shares (shared_by); + +-- Create recipe ratings table +CREATE TABLE IF NOT EXISTS recipe_ratings ( + id SERIAL PRIMARY KEY, + recipe_id INTEGER NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + rating INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT recipe_ratings_rating_check CHECK (rating >= 1 AND rating <= 5), + UNIQUE(recipe_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_recipe_ratings_recipe_id ON recipe_ratings (recipe_id); +CREATE INDEX IF NOT EXISTS idx_recipe_ratings_user_id ON recipe_ratings (user_id); + +-- Create recipe comments table +CREATE TABLE IF NOT EXISTS recipe_comments ( + id SERIAL PRIMARY KEY, + recipe_id INTEGER NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + content TEXT NOT NULL, + parent_comment_id INTEGER REFERENCES recipe_comments(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_deleted BOOLEAN DEFAULT FALSE +); + +CREATE INDEX IF NOT EXISTS idx_recipe_comments_recipe_id ON recipe_comments (recipe_id); +CREATE INDEX IF NOT EXISTS idx_recipe_comments_user_id ON recipe_comments (user_id); +CREATE INDEX IF NOT EXISTS idx_recipe_comments_parent_comment_id ON recipe_comments (parent_comment_id); + -- Create default admin user (password: admin123) -- Password hash generated with bcrypt for 'admin123' INSERT INTO users (username, email, password_hash, first_name, last_name, display_name, is_admin) From 5334733573dc2488bd30078102afee5772a33319 Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:03:27 +0200 Subject: [PATCH 02/11] Add my-recipes-chart-aws for aws --- aws/EKS_DEPLOYMENT.md | 225 ++++++++++++++++++ aws/my-recipes-chart-aws/Chart.yaml | 14 ++ .../add-missing-tables-configmap.yaml | 45 ++++ .../templates/add-missing-tables-job.yaml | 49 ++++ .../templates/admin-init-configmap.yaml | 99 ++++++++ .../templates/admin-init-job.yaml | 75 ++++++ .../templates/app-secrets.yaml | 35 +++ .../templates/backend-deployment.yaml | 119 +++++++++ .../templates/backend-service.yaml | 17 ++ .../templates/db-migration-configmap.yaml | 54 +++++ .../templates/db-migration-job.yaml | 69 ++++++ .../templates/db-schema-configmap.yaml | 134 +++++++++++ .../templates/db-secret.yaml | 23 ++ .../templates/db-service.yaml | 39 +++ .../templates/db-statefulset.yaml | 89 +++++++ .../templates/frontend-deployment.yaml | 57 +++++ .../templates/frontend-service.yaml | 17 ++ .../templates/ingress.yaml | 89 +++++++ aws/my-recipes-chart-aws/values.yaml | 171 +++++++++++++ aws/values.yaml | 97 ++++++++ 20 files changed, 1517 insertions(+) create mode 100644 aws/EKS_DEPLOYMENT.md create mode 100644 aws/my-recipes-chart-aws/Chart.yaml create mode 100644 aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml create mode 100644 aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml create mode 100644 aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml create mode 100644 aws/my-recipes-chart-aws/templates/admin-init-job.yaml create mode 100644 aws/my-recipes-chart-aws/templates/app-secrets.yaml create mode 100644 aws/my-recipes-chart-aws/templates/backend-deployment.yaml create mode 100644 aws/my-recipes-chart-aws/templates/backend-service.yaml create mode 100644 aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml create mode 100644 aws/my-recipes-chart-aws/templates/db-migration-job.yaml create mode 100644 aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml create mode 100644 aws/my-recipes-chart-aws/templates/db-secret.yaml create mode 100644 aws/my-recipes-chart-aws/templates/db-service.yaml create mode 100644 aws/my-recipes-chart-aws/templates/db-statefulset.yaml create mode 100644 aws/my-recipes-chart-aws/templates/frontend-deployment.yaml create mode 100644 aws/my-recipes-chart-aws/templates/frontend-service.yaml create mode 100644 aws/my-recipes-chart-aws/templates/ingress.yaml create mode 100644 aws/my-recipes-chart-aws/values.yaml create mode 100644 aws/values.yaml diff --git a/aws/EKS_DEPLOYMENT.md b/aws/EKS_DEPLOYMENT.md new file mode 100644 index 0000000..f447748 --- /dev/null +++ b/aws/EKS_DEPLOYMENT.md @@ -0,0 +1,225 @@ +# AWS EKS Deployment Guide + +This directory contains the Helm chart and configuration for deploying My Recipes application to Amazon EKS (Elastic Kubernetes Service). + +## Structure + +``` +aws/ +├── my-recipes-chart/ # Base Helm chart with default values +│ ├── Chart.yaml +│ ├── values.yaml # Base configuration (don't modify directly) +│ └── templates/ # Kubernetes resource templates +└── values.yaml # Project-specific values (override base values) +``` + +## Prerequisites + +1. **AWS CLI** - Configured with appropriate credentials +2. **kubectl** - Kubernetes command-line tool +3. **Helm 3** - Package manager for Kubernetes +4. **eksctl** (optional) - For creating EKS clusters + +## Setup Steps + +### 1. Create EKS Cluster (if not already exists) + +```bash +eksctl create cluster \ + --name my-recipes-cluster \ + --region eu-central-1 \ + --nodegroup-name standard-workers \ + --node-type t3.medium \ + --nodes 2 \ + --nodes-min 1 \ + --nodes-max 3 +``` + +### 2. Configure kubectl + +```bash +aws eks update-kubeconfig --region eu-central-1 --name my-recipes-cluster +``` + +### 3. Create Namespace + +```bash +kubectl create namespace my-apps +``` + +### 4. Install Ingress Controller (if not already installed) + +For AWS ALB Ingress Controller: +```bash +# Install AWS Load Balancer Controller +helm repo add eks https://aws.github.io/eks-charts +helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ + -n kube-system \ + --set clusterName=my-recipes-cluster +``` + +Or for NGINX Ingress Controller: +```bash +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm install nginx-ingress ingress-nginx/ingress-nginx \ + -n ingress-nginx --create-namespace +``` + +### 5. Install cert-manager (for SSL certificates) + +```bash +helm repo add jetstack https://charts.jetstack.io +helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --set installCRDs=true +``` + +### 6. Configure values.yaml + +Edit `values.yaml` in this directory and update: + +- **Container images**: Update ECR repository URLs +- **Domain names**: Replace `` with your actual domain +- **S3 credentials**: Add your AWS access key and secret key +- **Database**: Configure RDS connection details +- **OAuth**: Update redirect URIs with your domain + +### 7. Create S3 Bucket for Backups + +```bash +aws s3 mb s3://my-recipes-backups --region eu-central-1 +``` + +### 8. Push Docker Images to ECR + +```bash +# Create ECR repositories +aws ecr create-repository --repository-name my-recipes-backend --region eu-central-1 +aws ecr create-repository --repository-name my-recipes-frontend --region eu-central-1 + +# Login to ECR +aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin .dkr.ecr.eu-central-1.amazonaws.com + +# Build and push backend +cd backend +docker build -t my-recipes-backend . +docker tag my-recipes-backend:latest .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend:latest +docker push .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend:latest + +# Build and push frontend +cd ../frontend +docker build -t my-recipes-frontend . +docker tag my-recipes-frontend:latest .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend:latest +docker push .dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend:latest +``` + +### 9. Deploy with Helm + +```bash +# From the aws directory +helm install my-recipes ./my-recipes-chart \ + -f values.yaml \ + -n my-apps +``` + +### 10. Verify Deployment + +```bash +# Check pods +kubectl get pods -n my-apps + +# Check services +kubectl get svc -n my-apps + +# Check ingress +kubectl get ingress -n my-apps + +# View logs +kubectl logs -f deployment/my-recipes-backend -n my-apps +``` + +## Upgrading + +To update the deployment: + +```bash +# Update values.yaml with new configuration +helm upgrade my-recipes ./my-recipes-chart \ + -f values.yaml \ + -n my-apps +``` + +## Using AWS RDS (Recommended for Production) + +1. Create RDS PostgreSQL instance +2. Configure security groups to allow EKS node group access +3. Update `database` section in `values.yaml` with RDS connection details +4. The chart will automatically use external database instead of in-cluster PostgreSQL + +## Using S3 for Backups + +The application is configured to use AWS S3 for database backups instead of Cloudflare R2. Ensure: + +1. S3 bucket exists and is accessible +2. AWS credentials have appropriate permissions: + - `s3:PutObject` + - `s3:GetObject` + - `s3:ListBucket` + - `s3:DeleteObject` + +## Environment Variables + +The chart automatically creates secrets from `values.yaml`: +- Database credentials +- OAuth client secrets +- Email SMTP credentials +- S3 access keys + +All sensitive data should be stored in AWS Secrets Manager in production and referenced via External Secrets Operator. + +## Monitoring + +To view application logs: + +```bash +# Backend logs +kubectl logs -f deployment/my-recipes-backend -n my-apps + +# Frontend logs +kubectl logs -f deployment/my-recipes-frontend -n my-apps + +# Database logs (if using in-cluster DB) +kubectl logs -f statefulset/my-recipes-db -n my-apps +``` + +## Troubleshooting + +### Pods not starting +```bash +kubectl describe pod -n my-apps +``` + +### Database connection issues +```bash +kubectl exec -it deployment/my-recipes-backend -n my-apps -- env | grep DB_ +``` + +### Ingress not working +```bash +kubectl describe ingress -n my-apps +``` + +## Uninstall + +```bash +helm uninstall my-recipes -n my-apps +``` + +## Cost Optimization + +For non-production environments: +- Reduce replica counts to 1 +- Use smaller instance types (t3.small) +- Use in-cluster PostgreSQL instead of RDS +- Configure cluster autoscaling diff --git a/aws/my-recipes-chart-aws/Chart.yaml b/aws/my-recipes-chart-aws/Chart.yaml new file mode 100644 index 0000000..86c815b --- /dev/null +++ b/aws/my-recipes-chart-aws/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: my-recipes +description: Complete recipe management application with PostgreSQL, FastAPI backend, and React frontend +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - recipes + - fastapi + - react + - postgresql +maintainers: + - name: Development Team + diff --git a/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml b/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml new file mode 100644 index 0000000..d15cad9 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-add-missing-tables + namespace: {{ .Values.global.namespace }} +data: + add-tables.sql: | + -- Create grocery lists table + CREATE TABLE IF NOT EXISTS grocery_lists ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + items TEXT[] NOT NULL DEFAULT '{}', + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_pinned BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create grocery list shares table + CREATE TABLE IF NOT EXISTS grocery_list_shares ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, + shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + can_edit BOOLEAN DEFAULT FALSE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(list_id, shared_with_user_id) + ); + + CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); + + -- Create notifications table + CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type TEXT NOT NULL, + message TEXT NOT NULL, + related_id INTEGER, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); + CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml b/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml new file mode 100644 index 0000000..e0ca1bc --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml @@ -0,0 +1,49 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-add-missing-tables + namespace: {{ .Values.global.namespace }} + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-weight": "6" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + template: + spec: + restartPolicy: Never + containers: + - name: add-tables + image: postgres:16-alpine + env: + - name: PGHOST + value: {{ .Release.Name }}-db + - name: PGPORT + value: "{{ .Values.postgres.port }}" + - name: PGDATABASE + value: {{ .Values.postgres.database }} + - name: PGUSER + value: {{ .Values.postgres.user }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + command: + - sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database ready, adding missing tables..." + psql -v ON_ERROR_STOP=1 -f /sql/add-tables.sql + echo "Tables added successfully!" + volumeMounts: + - name: sql + mountPath: /sql + volumes: + - name: sql + configMap: + name: {{ .Release.Name }}-add-missing-tables diff --git a/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml b/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml new file mode 100644 index 0000000..7bda3cd --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml @@ -0,0 +1,99 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-admin-init + namespace: {{ .Values.global.namespace }} +data: + create-admin.py: | + #!/usr/bin/env python3 + import os + import sys + import psycopg2 + import bcrypt + from time import sleep + + def wait_for_db(): + """Wait for database to be ready""" + max_retries = 30 + retry_count = 0 + + while retry_count < max_retries: + try: + conn = psycopg2.connect( + host=os.environ['DB_HOST'], + port=os.environ['DB_PORT'], + database=os.environ['DB_NAME'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'] + ) + conn.close() + print("✓ Database is ready") + return True + except Exception as e: + retry_count += 1 + print(f"Waiting for database... ({retry_count}/{max_retries})") + sleep(2) + + print("✗ Database connection timeout") + return False + + def create_admin_user(): + """Create admin user if not exists""" + try: + # Hash the password + password = os.environ.get('ADMIN_PASSWORD', 'admin123') + password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + # Connect to database + conn = psycopg2.connect( + host=os.environ['DB_HOST'], + port=os.environ['DB_PORT'], + database=os.environ['DB_NAME'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'] + ) + cur = conn.cursor() + + # Insert admin user + cur.execute(""" + INSERT INTO users (username, email, password_hash, first_name, last_name, display_name, is_admin) + VALUES (%s, %s, %s, %s, %s, %s, %s) + ON CONFLICT (username) DO UPDATE SET + email = EXCLUDED.email, + password_hash = EXCLUDED.password_hash, + first_name = EXCLUDED.first_name, + last_name = EXCLUDED.last_name, + display_name = EXCLUDED.display_name, + is_admin = EXCLUDED.is_admin + """, ( + os.environ.get('ADMIN_USERNAME', 'admin'), + os.environ.get('ADMIN_EMAIL', 'admin@myrecipes.local'), + password_hash, + os.environ.get('ADMIN_FIRST_NAME', 'Admin'), + os.environ.get('ADMIN_LAST_NAME', 'User'), + os.environ.get('ADMIN_DISPLAY_NAME', 'מנהל'), + True + )) + + conn.commit() + cur.close() + conn.close() + + print(f"✓ Admin user '{os.environ.get('ADMIN_USERNAME', 'admin')}' created/updated successfully") + return True + + except Exception as e: + print(f"✗ Error creating admin user: {e}") + return False + + if __name__ == "__main__": + print("Starting admin user initialization...") + + if not wait_for_db(): + sys.exit(1) + + if not create_admin_user(): + sys.exit(1) + + print("✓ Admin user initialization completed") + sys.exit(0) diff --git a/aws/my-recipes-chart-aws/templates/admin-init-job.yaml b/aws/my-recipes-chart-aws/templates/admin-init-job.yaml new file mode 100644 index 0000000..511ec36 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/admin-init-job.yaml @@ -0,0 +1,75 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-admin-init-{{ .Release.Revision }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-admin-init + component: init + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "10" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + app: {{ .Release.Name }}-admin-init + spec: + restartPolicy: Never + containers: + - name: admin-init + image: python:3.12-slim + command: + - /bin/sh + - -c + - | + pip install --no-cache-dir psycopg2-binary bcrypt > /dev/null 2>&1 + python3 /scripts/create-admin.py + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + - name: ADMIN_USERNAME + value: {{ .Values.admin.username | quote }} + - name: ADMIN_EMAIL + value: {{ .Values.admin.email | quote }} + - name: ADMIN_PASSWORD + value: {{ .Values.admin.password | quote }} + - name: ADMIN_FIRST_NAME + value: {{ .Values.admin.firstName | quote }} + - name: ADMIN_LAST_NAME + value: {{ .Values.admin.lastName | quote }} + - name: ADMIN_DISPLAY_NAME + value: {{ .Values.admin.displayName | quote }} + volumeMounts: + - name: init-script + mountPath: /scripts + volumes: + - name: init-script + configMap: + name: {{ .Release.Name }}-admin-init + defaultMode: 0755 diff --git a/aws/my-recipes-chart-aws/templates/app-secrets.yaml b/aws/my-recipes-chart-aws/templates/app-secrets.yaml new file mode 100644 index 0000000..93285f3 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/app-secrets.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-app-secrets + namespace: {{ .Values.global.namespace }} +type: Opaque +stringData: + # Google OAuth + GOOGLE_CLIENT_ID: {{ .Values.oauth.google.clientId | quote }} + GOOGLE_CLIENT_SECRET: {{ .Values.oauth.google.clientSecret | quote }} + GOOGLE_REDIRECT_URI: {{ .Values.oauth.google.redirectUri | quote }} + + # Microsoft Entra ID (Azure AD) OAuth + AZURE_CLIENT_ID: {{ .Values.oauth.azure.clientId | quote }} + AZURE_CLIENT_SECRET: {{ .Values.oauth.azure.clientSecret | quote }} + AZURE_TENANT_ID: {{ .Values.oauth.azure.tenantId | quote }} + AZURE_REDIRECT_URI: {{ .Values.oauth.azure.redirectUri | quote }} + + # Email Configuration + SMTP_HOST: {{ .Values.email.smtpHost | quote }} + SMTP_PORT: {{ .Values.email.smtpPort | quote }} + SMTP_USER: {{ .Values.email.smtpUser | quote }} + SMTP_PASSWORD: {{ .Values.email.smtpPassword | quote }} + SMTP_FROM: {{ .Values.email.smtpFrom | quote }} + + # Frontend URL for redirects + FRONTEND_URL: {{ .Values.frontend.externalUrl | quote }} + + # S3 Backup Configuration + S3_ENDPOINT: {{ .Values.s3.endpoint | quote }} + S3_ACCESS_KEY: {{ .Values.s3.accessKey | quote }} + S3_SECRET_KEY: {{ .Values.s3.secretKey | quote }} + S3_BUCKET_NAME: {{ .Values.s3.bucketName | quote }} + S3_REGION: {{ .Values.s3.region | quote }} + BACKUP_INTERVAL: {{ .Values.s3.backupInterval | quote }} diff --git a/aws/my-recipes-chart-aws/templates/backend-deployment.yaml b/aws/my-recipes-chart-aws/templates/backend-deployment.yaml new file mode 100644 index 0000000..b7379bd --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/backend-deployment.yaml @@ -0,0 +1,119 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + replicas: {{ .Values.backend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend + spec: + initContainers: + - name: db-migration + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $DB_HOST -U $DB_USER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database is ready, running migration..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql + echo "Migration completed successfully" + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + volumeMounts: + - name: migration-script + mountPath: /migration + containers: + - name: {{ .Values.backend.name }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.service.targetPort }} + name: http + protocol: TCP + env: + {{- if .Values.backend.env }} + {{- range $key, $value := .Values.backend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-db-credentials + - secretRef: + name: {{ .Release.Name }}-app-secrets + startupProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 30 + livenessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.backend.resources.requests.cpu }} + memory: {{ .Values.backend.resources.requests.memory }} + limits: + cpu: {{ .Values.backend.resources.limits.cpu }} + memory: {{ .Values.backend.resources.limits.memory }} + volumes: + - name: migration-script + configMap: + name: {{ .Release.Name }}-db-migration + diff --git a/aws/my-recipes-chart-aws/templates/backend-service.yaml b/aws/my-recipes-chart-aws/templates/backend-service.yaml new file mode 100644 index 0000000..6608df6 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/backend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.backend.name }} diff --git a/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml b/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml new file mode 100644 index 0000000..5a7c4da --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-migration + namespace: {{ .Values.global.namespace }} +data: + migrate.sql: | + -- Add made_by column to recipes if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'recipes' AND column_name = 'made_by' + ) THEN + ALTER TABLE recipes ADD COLUMN made_by TEXT; + END IF; + END $$; + + -- Create index if it doesn't exist + CREATE INDEX IF NOT EXISTS idx_recipes_made_by ON recipes (made_by); + + -- Add is_admin column to users if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_admin' + ) THEN + ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; + END IF; + END $$; + + -- Add auth_provider column to users if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'auth_provider' + ) THEN + ALTER TABLE users ADD COLUMN auth_provider TEXT DEFAULT 'local'; + END IF; + END $$; + + -- Verify recipes schema + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'recipes' + ORDER BY ordinal_position; + + -- Verify users schema + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'users' + ORDER BY ordinal_position; diff --git a/aws/my-recipes-chart-aws/templates/db-migration-job.yaml b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml new file mode 100644 index 0000000..f153342 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml @@ -0,0 +1,69 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-db-migration-{{ .Release.Revision }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-db-migration + component: migration + annotations: + "helm.sh/hook": post-upgrade,post-install + "helm.sh/hook-weight": "5" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + app: {{ .Release.Name }}-db-migration + spec: + restartPolicy: Never + containers: + - name: migrate + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $DB_HOST -U $DB_USER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database is ready, running migration..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql + echo "Migration completed successfully" + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + volumeMounts: + - name: migration-script + mountPath: /migration + volumes: + - name: migration-script + configMap: + name: {{ .Release.Name }}-db-migration + diff --git a/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml b/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml new file mode 100644 index 0000000..c6ee6dc --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml @@ -0,0 +1,134 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-schema + namespace: {{ .Values.global.namespace }} +data: + schema.sql: | + -- Create users table + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + first_name TEXT, + last_name TEXT, + display_name TEXT NOT NULL, + is_admin BOOLEAN DEFAULT FALSE, + auth_provider TEXT DEFAULT 'local', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_users_username ON users (username); + CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); + + -- Create recipes table (matching backend schema with TEXT[] arrays) + CREATE TABLE IF NOT EXISTS recipes ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack + time_minutes INTEGER NOT NULL, + tags TEXT[] NOT NULL DEFAULT '{}', -- {"מהיר", "בריא"} + ingredients TEXT[] NOT NULL DEFAULT '{}', -- {"ביצה", "עגבניה", "מלח"} + steps TEXT[] NOT NULL DEFAULT '{}', -- {"לחתוך", "לבשל", ...} + image TEXT, -- Base64-encoded image or image URL + made_by TEXT, -- Person who created this recipe version + user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Recipe owner + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Indexes for filters + 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_user_id + ON recipes (user_id); + + -- Add new columns to existing users table + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'first_name' + ) THEN + ALTER TABLE users ADD COLUMN first_name TEXT; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'last_name' + ) THEN + ALTER TABLE users ADD COLUMN last_name TEXT; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'display_name' + ) THEN + ALTER TABLE users ADD COLUMN display_name TEXT; + -- Set display_name to username for existing users + UPDATE users SET display_name = username WHERE display_name IS NULL; + ALTER TABLE users ALTER COLUMN display_name SET NOT NULL; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_admin' + ) THEN + ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; + END IF; + END $$; + + -- Create grocery lists table + CREATE TABLE IF NOT EXISTS grocery_lists ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + items TEXT[] NOT NULL DEFAULT '{}', + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_pinned BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create grocery list shares table + CREATE TABLE IF NOT EXISTS grocery_list_shares ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, + shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + can_edit BOOLEAN DEFAULT FALSE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(list_id, shared_with_user_id) + ); + + CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); + + -- Create notifications table + CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type TEXT NOT NULL, + message TEXT NOT NULL, + related_id INTEGER, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); + CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/my-recipes-chart-aws/templates/db-secret.yaml b/aws/my-recipes-chart-aws/templates/db-secret.yaml new file mode 100644 index 0000000..aef2e21 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-db-credentials + namespace: {{ .Values.global.namespace }} +type: Opaque +stringData: + {{- if .Values.database }} + # External database (e.g., AWS RDS) + DB_HOST: {{ .Values.database.host | quote }} + DB_PORT: {{ .Values.database.port | quote }} + DB_NAME: {{ .Values.database.name | quote }} + DB_USER: {{ .Values.database.user | quote }} + DB_PASSWORD: {{ .Values.database.password | quote }} + {{- else }} + # In-cluster PostgreSQL + DB_HOST: {{ printf "%s-%s-headless.%s.svc.cluster.local" .Release.Name .Values.postgres.name .Values.global.namespace }} + DB_PORT: "{{ .Values.postgres.port }}" + DB_NAME: {{ .Values.postgres.database | quote }} + DB_USER: {{ .Values.postgres.user | quote }} + DB_PASSWORD: {{ .Values.postgres.password | quote }} + {{- end }} + diff --git a/aws/my-recipes-chart-aws/templates/db-service.yaml b/aws/my-recipes-chart-aws/templates/db-service.yaml new file mode 100644 index 0000000..c4704d3 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-service.yaml @@ -0,0 +1,39 @@ +{{- if not .Values.database }} +{{- /* Only deploy in-cluster PostgreSQL services if external database is not configured */ -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + clusterIP: None + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + type: {{ .Values.postgres.service.type }} + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.service.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP +{{- end }} + diff --git a/aws/my-recipes-chart-aws/templates/db-statefulset.yaml b/aws/my-recipes-chart-aws/templates/db-statefulset.yaml new file mode 100644 index 0000000..93af59a --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/db-statefulset.yaml @@ -0,0 +1,89 @@ +{{- if not .Values.database }} +{{- /* Only deploy in-cluster PostgreSQL if external database is not configured */ -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + serviceName: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database + spec: + containers: + - name: postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + imagePullPolicy: {{ .Values.postgres.image.pullPolicy }} + ports: + - containerPort: {{ .Values.postgres.port }} + name: postgres + protocol: TCP + env: + - name: POSTGRES_USER + value: {{ .Values.postgres.user | quote }} + - name: POSTGRES_PASSWORD + value: {{ .Values.postgres.password | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgres.database | quote }} + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + - name: init-sql + mountPath: /docker-entrypoint-initdb.d + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.postgres.resources.requests.cpu }} + memory: {{ .Values.postgres.resources.requests.memory }} + limits: + cpu: {{ .Values.postgres.resources.limits.cpu }} + memory: {{ .Values.postgres.resources.limits.memory }} + volumes: + - name: init-sql + configMap: + name: {{ .Release.Name }}-db-schema + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.postgres.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.postgres.persistence.size }} + {{- if .Values.postgres.persistence.storageClass }} + storageClassName: {{ .Values.postgres.persistence.storageClass | quote }} + {{- end }} +{{- end }} + diff --git a/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml b/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml new file mode 100644 index 0000000..8973e53 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend + spec: + containers: + - name: {{ .Values.frontend.name }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.service.targetPort }} + name: http + protocol: TCP + {{- with .Values.frontend.env }} + env: + {{- range $key, $value := . }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.frontend.resources.requests.cpu }} + memory: {{ .Values.frontend.resources.requests.memory }} + limits: + cpu: {{ .Values.frontend.resources.limits.cpu }} + memory: {{ .Values.frontend.resources.limits.memory }} diff --git a/aws/my-recipes-chart-aws/templates/frontend-service.yaml b/aws/my-recipes-chart-aws/templates/frontend-service.yaml new file mode 100644 index 0000000..9427830 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} diff --git a/aws/my-recipes-chart-aws/templates/ingress.yaml b/aws/my-recipes-chart-aws/templates/ingress.yaml new file mode 100644 index 0000000..d106c59 --- /dev/null +++ b/aws/my-recipes-chart-aws/templates/ingress.yaml @@ -0,0 +1,89 @@ +{{- if .Values.frontend.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-frontend + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-frontend + component: frontend + {{- with .Values.frontend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.frontend.ingress.className }} + ingressClassName: {{ .Values.frontend.ingress.className }} + {{- end }} + rules: + {{- range .Values.frontend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-{{ $.Values.frontend.name }} + port: + number: {{ $.Values.frontend.service.port }} + {{- end }} + {{- end }} + {{- if .Values.frontend.ingress.tls }} + tls: + {{- range .Values.frontend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} + +--- + +{{- if .Values.backend.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-backend + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-backend + component: backend + {{- with .Values.backend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.backend.ingress.className }} + ingressClassName: {{ .Values.backend.ingress.className }} + {{- end }} + rules: + {{- range .Values.backend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-{{ $.Values.backend.name }} + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + {{- end }} + {{- if .Values.backend.ingress.tls }} + tls: + {{- range .Values.backend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} diff --git a/aws/my-recipes-chart-aws/values.yaml b/aws/my-recipes-chart-aws/values.yaml new file mode 100644 index 0000000..0fafadd --- /dev/null +++ b/aws/my-recipes-chart-aws/values.yaml @@ -0,0 +1,171 @@ +global: + namespace: my-apps + imagePullSecrets: [] + +# Backend configuration +backend: + name: backend + replicaCount: 2 + image: + repository: harbor.dvirlabs.com/my-apps/my-recipes-backend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 8000 + targetPort: 8000 + + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + env: + PYTHONUNBUFFERED: "1" + + # Secrets are created in db-secret.yaml + # These are passed via envFrom secretRef + + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: api-my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-my-recipes-tls + hosts: + - api-my-recipes.dvirlabs.com + +# Frontend configuration +frontend: + name: frontend + replicaCount: 2 + image: + repository: harbor.dvirlabs.com/my-apps/my-recipes-frontend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 80 + targetPort: 80 + + env: + API_BASE: "https://api-my-recipes.dvirlabs.com" + + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + + ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: my-recipes-tls + hosts: + - my-recipes.dvirlabs.com + externalUrl: "https://my-recipes.dvirlabs.com" + +# PostgreSQL configuration +postgres: + name: db + image: + repository: postgres + tag: "16-alpine" + pullPolicy: IfNotPresent + + user: recipes_user + password: recipes_password + database: recipes_db + port: 5432 + + service: + type: ClusterIP + port: 5432 + targetPort: 5432 + + persistence: + enabled: true + accessMode: ReadWriteOnce + storageClass: "nfs-client" + size: 10Gi + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# OAuth Configuration +oauth: + google: + clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" + clientSecret: "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" + redirectUri: "https://api-my-recipes.dvirlabs.com/auth/google/callback" + + azure: + clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" + clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" + tenantId: "consumers" + redirectUri: "https://api-my-recipes.dvirlabs.com/auth/azure/callback" + +# Email Configuration +email: + smtpHost: "smtp.gmail.com" + smtpPort: "587" + smtpUser: "dvirlabs@gmail.com" + smtpPassword: "agaanrhbbazbdytv" + smtpFrom: "dvirlabs@gmail.com" + +# S3 Backup Configuration +s3: + endpoint: "https://s3.amazonaws.com" # Can be overridden for specific regions + accessKey: "" # Set this in project-specific values.yaml + secretKey: "" # Set this in project-specific values.yaml + bucketName: "" # Set this in project-specific values.yaml + region: "us-east-1" # Set this in project-specific values.yaml + backupInterval: "weekly" # Options: test (1 min), daily, weekly + +# Ingress configuration +ingress: + enabled: false # Individual frontend/backend ingress resources handle routing instead + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + backend: frontend + tls: + - secretName: recipes-tls + hosts: + - my-recipes.dvirlabs.com + diff --git a/aws/values.yaml b/aws/values.yaml new file mode 100644 index 0000000..f9a5591 --- /dev/null +++ b/aws/values.yaml @@ -0,0 +1,97 @@ +# Project-specific values for AWS EKS deployment +# This file overrides the base values in my-recipes-chart/values.yaml + +global: + namespace: my-apps + +# Backend configuration +backend: + replicaCount: 2 + image: + repository: /my-recipes-backend # Update with your ECR repository + tag: "latest" + + ingress: + className: "alb" + hosts: + - host: api-my-recipes.aws-dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-my-recipes-tls + hosts: + - api-my-recipes.aws-dvirlabs.com + +# Frontend configuration +frontend: + replicaCount: 2 + image: + repository: /my-recipes-frontend # Update with your ECR repository + tag: "latest" + + env: + API_BASE: "https://api-my-recipes.aws-dvirlabs.com" + + ingress: + className: "alb" + hosts: + - host: my-recipes.aws-dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: my-recipes-tls + hosts: + - my-recipes.aws-dvirlabs.com + + externalUrl: "https://my-recipes.aws-dvirlabs.com" + +# PostgreSQL configuration +postgres: + # For AWS RDS, set this to use external database + # Leave enabled: true to use in-cluster database + enabled: false # Set to false if using RDS + + # If using RDS, these values are ignored but kept for reference + persistence: + storageClass: "gp3" # EKS default storage class + size: 20Gi + +# OAuth Configuration +oauth: + google: + clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" + clientSecret: "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" + redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/google/callback" + + azure: + clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" + clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" + tenantId: "consumers" + redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/azure/callback" + +# Email Configuration +email: + smtpHost: "smtp.gmail.com" + smtpPort: "587" + smtpUser: "dvirlabs@gmail.com" + smtpPassword: "agaanrhbbazbdytv" + smtpFrom: "dvirlabs@gmail.com" + +# S3 Backup Configuration for AWS +s3: + endpoint: "https://s3.eu-central-1.amazonaws.com" # Update with your region + accessKey: "AKIAXXXXXXXXXXXXXXXX" # Replace with your AWS Access Key + secretKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with your AWS Secret Key + bucketName: "my-recipes-backups" # Update with your S3 bucket name + region: "eu-central-1" # Update with your region + backupInterval: "weekly" + +# Database connection for AWS RDS (used when postgres.enabled: false) +database: + host: "my-recipes-rds.chw4omcguqv7.eu-central-1.rds.amazonaws.com" + port: "5432" + name: "recipes_db" + user: "recipes_user" + password: "recipes_password" # Store securely in AWS Secrets Manager in production From 3562c806c7c271f66dae7ad0657826650ce4339e Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:46:15 +0200 Subject: [PATCH 03/11] Add admin configuration --- aws/my-recipes-chart-aws/values.yaml | 9 +++++++++ aws/values.yaml | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/aws/my-recipes-chart-aws/values.yaml b/aws/my-recipes-chart-aws/values.yaml index 0fafadd..68ed544 100644 --- a/aws/my-recipes-chart-aws/values.yaml +++ b/aws/my-recipes-chart-aws/values.yaml @@ -152,6 +152,15 @@ s3: region: "us-east-1" # Set this in project-specific values.yaml backupInterval: "weekly" # Options: test (1 min), daily, weekly +# Admin User Configuration +admin: + username: "admin" + email: "admin@example.com" + password: "admin123" # Change this in production! + firstName: "Admin" + lastName: "User" + displayName: "Admin User" + # Ingress configuration ingress: enabled: false # Individual frontend/backend ingress resources handle routing instead diff --git a/aws/values.yaml b/aws/values.yaml index f9a5591..3a39473 100644 --- a/aws/values.yaml +++ b/aws/values.yaml @@ -88,6 +88,15 @@ s3: region: "eu-central-1" # Update with your region backupInterval: "weekly" +# Admin User Configuration +admin: + username: "admin" + email: "dvirlabs@gmail.com" + password: "AdminPassword123!" # Change this after first login! + firstName: "Dvir" + lastName: "Admin" + displayName: "Dvir Admin" + # Database connection for AWS RDS (used when postgres.enabled: false) database: host: "my-recipes-rds.chw4omcguqv7.eu-central-1.rds.amazonaws.com" From 42f0bc7508af1d972f22c5cd88c0da4e3d21f99e Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:51:29 +0200 Subject: [PATCH 04/11] Update alb --- aws/my-recipes-chart-aws/values.yaml | 18 ++++++++++-------- aws/values.yaml | 20 ++++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/aws/my-recipes-chart-aws/values.yaml b/aws/my-recipes-chart-aws/values.yaml index 68ed544..31d7804 100644 --- a/aws/my-recipes-chart-aws/values.yaml +++ b/aws/my-recipes-chart-aws/values.yaml @@ -32,11 +32,12 @@ backend: ingress: enabled: true - className: "traefik" + className: "alb" annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: "true" - cert-manager.io/cluster-issuer: "letsencrypt-prod" + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + alb.ingress.kubernetes.io/certificate-arn: "" # Set in project-specific values hosts: - host: api-my-recipes.dvirlabs.com paths: @@ -74,11 +75,12 @@ frontend: ingress: enabled: true - className: "traefik" + className: "alb" annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: "true" - cert-manager.io/cluster-issuer: "letsencrypt-prod" + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + alb.ingress.kubernetes.io/certificate-arn: "" # Set in project-specific values hosts: - host: my-recipes.dvirlabs.com paths: diff --git a/aws/values.yaml b/aws/values.yaml index 3a39473..414c4f8 100644 --- a/aws/values.yaml +++ b/aws/values.yaml @@ -13,15 +13,17 @@ backend: ingress: className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + # Add your ACM certificate ARN below if you have one + # alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..." hosts: - host: api-my-recipes.aws-dvirlabs.com paths: - path: / pathType: Prefix - tls: - - secretName: api-my-recipes-tls - hosts: - - api-my-recipes.aws-dvirlabs.com # Frontend configuration frontend: @@ -35,15 +37,17 @@ frontend: ingress: className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + # Add your ACM certificate ARN below if you have one + # alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..." hosts: - host: my-recipes.aws-dvirlabs.com paths: - path: / pathType: Prefix - tls: - - secretName: my-recipes-tls - hosts: - - my-recipes.aws-dvirlabs.com externalUrl: "https://my-recipes.aws-dvirlabs.com" From d020819ec098b9b280d3f01c2452eece7aec5435 Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:59:40 +0200 Subject: [PATCH 05/11] Update registry --- aws/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/values.yaml b/aws/values.yaml index 414c4f8..e526c3d 100644 --- a/aws/values.yaml +++ b/aws/values.yaml @@ -8,7 +8,7 @@ global: backend: replicaCount: 2 image: - repository: /my-recipes-backend # Update with your ECR repository + repository: 430842105273.dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend # Update with your ECR repository tag: "latest" ingress: @@ -29,7 +29,7 @@ backend: frontend: replicaCount: 2 image: - repository: /my-recipes-frontend # Update with your ECR repository + repository: 430842105273.dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend # Update with your ECR repository tag: "latest" env: From cf6c9e7bd7febe29694725d272caa8480233ae17 Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:50:49 +0200 Subject: [PATCH 06/11] Update schema.sql --- aws/my-recipes-chart-aws/Chart.yaml | 14 -- .../add-missing-tables-configmap.yaml | 45 ----- .../templates/add-missing-tables-job.yaml | 49 ----- .../templates/admin-init-configmap.yaml | 99 ---------- .../templates/admin-init-job.yaml | 75 -------- .../templates/app-secrets.yaml | 35 ---- .../templates/backend-deployment.yaml | 119 ------------ .../templates/backend-service.yaml | 17 -- .../templates/db-migration-configmap.yaml | 54 ------ .../templates/db-migration-job.yaml | 69 ------- .../templates/db-schema-configmap.yaml | 134 ------------- .../templates/db-secret.yaml | 23 --- .../templates/db-service.yaml | 39 ---- .../templates/db-statefulset.yaml | 89 --------- .../templates/frontend-deployment.yaml | 57 ------ .../templates/frontend-service.yaml | 17 -- .../templates/ingress.yaml | 89 --------- aws/my-recipes-chart-aws/values.yaml | 182 ------------------ aws/values.yaml | 110 ----------- 19 files changed, 1316 deletions(-) delete mode 100644 aws/my-recipes-chart-aws/Chart.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/admin-init-job.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/app-secrets.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/backend-deployment.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/backend-service.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/db-migration-job.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/db-secret.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/db-service.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/db-statefulset.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/frontend-deployment.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/frontend-service.yaml delete mode 100644 aws/my-recipes-chart-aws/templates/ingress.yaml delete mode 100644 aws/my-recipes-chart-aws/values.yaml delete mode 100644 aws/values.yaml diff --git a/aws/my-recipes-chart-aws/Chart.yaml b/aws/my-recipes-chart-aws/Chart.yaml deleted file mode 100644 index 86c815b..0000000 --- a/aws/my-recipes-chart-aws/Chart.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v2 -name: my-recipes -description: Complete recipe management application with PostgreSQL, FastAPI backend, and React frontend -type: application -version: 1.0.0 -appVersion: "1.0.0" -keywords: - - recipes - - fastapi - - react - - postgresql -maintainers: - - name: Development Team - diff --git a/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml b/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml deleted file mode 100644 index d15cad9..0000000 --- a/aws/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml +++ /dev/null @@ -1,45 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-add-missing-tables - namespace: {{ .Values.global.namespace }} -data: - add-tables.sql: | - -- Create grocery lists table - CREATE TABLE IF NOT EXISTS grocery_lists ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - items TEXT[] NOT NULL DEFAULT '{}', - owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - is_pinned BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Create grocery list shares table - CREATE TABLE IF NOT EXISTS grocery_list_shares ( - id SERIAL PRIMARY KEY, - list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, - shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - can_edit BOOLEAN DEFAULT FALSE, - shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(list_id, shared_with_user_id) - ); - - CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); - CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); - CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); - - -- Create notifications table - CREATE TABLE IF NOT EXISTS notifications ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - type TEXT NOT NULL, - message TEXT NOT NULL, - related_id INTEGER, - is_read BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); - CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml b/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml deleted file mode 100644 index e0ca1bc..0000000 --- a/aws/my-recipes-chart-aws/templates/add-missing-tables-job.yaml +++ /dev/null @@ -1,49 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ .Release.Name }}-add-missing-tables - namespace: {{ .Values.global.namespace }} - annotations: - "helm.sh/hook": post-upgrade - "helm.sh/hook-weight": "6" - "helm.sh/hook-delete-policy": before-hook-creation -spec: - template: - spec: - restartPolicy: Never - containers: - - name: add-tables - image: postgres:16-alpine - env: - - name: PGHOST - value: {{ .Release.Name }}-db - - name: PGPORT - value: "{{ .Values.postgres.port }}" - - name: PGDATABASE - value: {{ .Values.postgres.database }} - - name: PGUSER - value: {{ .Values.postgres.user }} - - name: PGPASSWORD - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PASSWORD - command: - - sh - - -c - - | - echo "Waiting for database to be ready..." - until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do - echo "Database not ready, waiting..." - sleep 2 - done - echo "Database ready, adding missing tables..." - psql -v ON_ERROR_STOP=1 -f /sql/add-tables.sql - echo "Tables added successfully!" - volumeMounts: - - name: sql - mountPath: /sql - volumes: - - name: sql - configMap: - name: {{ .Release.Name }}-add-missing-tables diff --git a/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml b/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml deleted file mode 100644 index 7bda3cd..0000000 --- a/aws/my-recipes-chart-aws/templates/admin-init-configmap.yaml +++ /dev/null @@ -1,99 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-admin-init - namespace: {{ .Values.global.namespace }} -data: - create-admin.py: | - #!/usr/bin/env python3 - import os - import sys - import psycopg2 - import bcrypt - from time import sleep - - def wait_for_db(): - """Wait for database to be ready""" - max_retries = 30 - retry_count = 0 - - while retry_count < max_retries: - try: - conn = psycopg2.connect( - host=os.environ['DB_HOST'], - port=os.environ['DB_PORT'], - database=os.environ['DB_NAME'], - user=os.environ['DB_USER'], - password=os.environ['DB_PASSWORD'] - ) - conn.close() - print("✓ Database is ready") - return True - except Exception as e: - retry_count += 1 - print(f"Waiting for database... ({retry_count}/{max_retries})") - sleep(2) - - print("✗ Database connection timeout") - return False - - def create_admin_user(): - """Create admin user if not exists""" - try: - # Hash the password - password = os.environ.get('ADMIN_PASSWORD', 'admin123') - password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') - - # Connect to database - conn = psycopg2.connect( - host=os.environ['DB_HOST'], - port=os.environ['DB_PORT'], - database=os.environ['DB_NAME'], - user=os.environ['DB_USER'], - password=os.environ['DB_PASSWORD'] - ) - cur = conn.cursor() - - # Insert admin user - cur.execute(""" - INSERT INTO users (username, email, password_hash, first_name, last_name, display_name, is_admin) - VALUES (%s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (username) DO UPDATE SET - email = EXCLUDED.email, - password_hash = EXCLUDED.password_hash, - first_name = EXCLUDED.first_name, - last_name = EXCLUDED.last_name, - display_name = EXCLUDED.display_name, - is_admin = EXCLUDED.is_admin - """, ( - os.environ.get('ADMIN_USERNAME', 'admin'), - os.environ.get('ADMIN_EMAIL', 'admin@myrecipes.local'), - password_hash, - os.environ.get('ADMIN_FIRST_NAME', 'Admin'), - os.environ.get('ADMIN_LAST_NAME', 'User'), - os.environ.get('ADMIN_DISPLAY_NAME', 'מנהל'), - True - )) - - conn.commit() - cur.close() - conn.close() - - print(f"✓ Admin user '{os.environ.get('ADMIN_USERNAME', 'admin')}' created/updated successfully") - return True - - except Exception as e: - print(f"✗ Error creating admin user: {e}") - return False - - if __name__ == "__main__": - print("Starting admin user initialization...") - - if not wait_for_db(): - sys.exit(1) - - if not create_admin_user(): - sys.exit(1) - - print("✓ Admin user initialization completed") - sys.exit(0) diff --git a/aws/my-recipes-chart-aws/templates/admin-init-job.yaml b/aws/my-recipes-chart-aws/templates/admin-init-job.yaml deleted file mode 100644 index 511ec36..0000000 --- a/aws/my-recipes-chart-aws/templates/admin-init-job.yaml +++ /dev/null @@ -1,75 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ .Release.Name }}-admin-init-{{ .Release.Revision }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-admin-init - component: init - annotations: - "helm.sh/hook": post-install,post-upgrade - "helm.sh/hook-weight": "10" - "helm.sh/hook-delete-policy": before-hook-creation -spec: - ttlSecondsAfterFinished: 300 - template: - metadata: - labels: - app: {{ .Release.Name }}-admin-init - spec: - restartPolicy: Never - containers: - - name: admin-init - image: python:3.12-slim - command: - - /bin/sh - - -c - - | - pip install --no-cache-dir psycopg2-binary bcrypt > /dev/null 2>&1 - python3 /scripts/create-admin.py - env: - - name: DB_HOST - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_HOST - - name: DB_PORT - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PORT - - name: DB_NAME - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_NAME - - name: DB_USER - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_USER - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PASSWORD - - name: ADMIN_USERNAME - value: {{ .Values.admin.username | quote }} - - name: ADMIN_EMAIL - value: {{ .Values.admin.email | quote }} - - name: ADMIN_PASSWORD - value: {{ .Values.admin.password | quote }} - - name: ADMIN_FIRST_NAME - value: {{ .Values.admin.firstName | quote }} - - name: ADMIN_LAST_NAME - value: {{ .Values.admin.lastName | quote }} - - name: ADMIN_DISPLAY_NAME - value: {{ .Values.admin.displayName | quote }} - volumeMounts: - - name: init-script - mountPath: /scripts - volumes: - - name: init-script - configMap: - name: {{ .Release.Name }}-admin-init - defaultMode: 0755 diff --git a/aws/my-recipes-chart-aws/templates/app-secrets.yaml b/aws/my-recipes-chart-aws/templates/app-secrets.yaml deleted file mode 100644 index 93285f3..0000000 --- a/aws/my-recipes-chart-aws/templates/app-secrets.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-app-secrets - namespace: {{ .Values.global.namespace }} -type: Opaque -stringData: - # Google OAuth - GOOGLE_CLIENT_ID: {{ .Values.oauth.google.clientId | quote }} - GOOGLE_CLIENT_SECRET: {{ .Values.oauth.google.clientSecret | quote }} - GOOGLE_REDIRECT_URI: {{ .Values.oauth.google.redirectUri | quote }} - - # Microsoft Entra ID (Azure AD) OAuth - AZURE_CLIENT_ID: {{ .Values.oauth.azure.clientId | quote }} - AZURE_CLIENT_SECRET: {{ .Values.oauth.azure.clientSecret | quote }} - AZURE_TENANT_ID: {{ .Values.oauth.azure.tenantId | quote }} - AZURE_REDIRECT_URI: {{ .Values.oauth.azure.redirectUri | quote }} - - # Email Configuration - SMTP_HOST: {{ .Values.email.smtpHost | quote }} - SMTP_PORT: {{ .Values.email.smtpPort | quote }} - SMTP_USER: {{ .Values.email.smtpUser | quote }} - SMTP_PASSWORD: {{ .Values.email.smtpPassword | quote }} - SMTP_FROM: {{ .Values.email.smtpFrom | quote }} - - # Frontend URL for redirects - FRONTEND_URL: {{ .Values.frontend.externalUrl | quote }} - - # S3 Backup Configuration - S3_ENDPOINT: {{ .Values.s3.endpoint | quote }} - S3_ACCESS_KEY: {{ .Values.s3.accessKey | quote }} - S3_SECRET_KEY: {{ .Values.s3.secretKey | quote }} - S3_BUCKET_NAME: {{ .Values.s3.bucketName | quote }} - S3_REGION: {{ .Values.s3.region | quote }} - BACKUP_INTERVAL: {{ .Values.s3.backupInterval | quote }} diff --git a/aws/my-recipes-chart-aws/templates/backend-deployment.yaml b/aws/my-recipes-chart-aws/templates/backend-deployment.yaml deleted file mode 100644 index b7379bd..0000000 --- a/aws/my-recipes-chart-aws/templates/backend-deployment.yaml +++ /dev/null @@ -1,119 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }}-{{ .Values.backend.name }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.backend.name }} - component: backend -spec: - replicas: {{ .Values.backend.replicaCount }} - selector: - matchLabels: - app: {{ .Release.Name }}-{{ .Values.backend.name }} - template: - metadata: - labels: - app: {{ .Release.Name }}-{{ .Values.backend.name }} - component: backend - spec: - initContainers: - - name: db-migration - image: postgres:16-alpine - command: - - /bin/sh - - -c - - | - echo "Waiting for database to be ready..." - until pg_isready -h $DB_HOST -U $DB_USER; do - echo "Database not ready, waiting..." - sleep 2 - done - echo "Database is ready, running migration..." - PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql - echo "Migration completed successfully" - env: - - name: DB_HOST - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_HOST - - name: DB_PORT - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PORT - - name: DB_NAME - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_NAME - - name: DB_USER - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_USER - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PASSWORD - volumeMounts: - - name: migration-script - mountPath: /migration - containers: - - name: {{ .Values.backend.name }} - image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" - imagePullPolicy: {{ .Values.backend.image.pullPolicy }} - ports: - - containerPort: {{ .Values.backend.service.targetPort }} - name: http - protocol: TCP - env: - {{- if .Values.backend.env }} - {{- range $key, $value := .Values.backend.env }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- end }} - envFrom: - - secretRef: - name: {{ .Release.Name }}-db-credentials - - secretRef: - name: {{ .Release.Name }}-app-secrets - startupProbe: - httpGet: - path: /docs - port: http - initialDelaySeconds: 15 - periodSeconds: 5 - timeoutSeconds: 3 - failureThreshold: 30 - livenessProbe: - httpGet: - path: /docs - port: http - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /docs - port: http - initialDelaySeconds: 10 - periodSeconds: 5 - timeoutSeconds: 3 - failureThreshold: 2 - resources: - requests: - cpu: {{ .Values.backend.resources.requests.cpu }} - memory: {{ .Values.backend.resources.requests.memory }} - limits: - cpu: {{ .Values.backend.resources.limits.cpu }} - memory: {{ .Values.backend.resources.limits.memory }} - volumes: - - name: migration-script - configMap: - name: {{ .Release.Name }}-db-migration - diff --git a/aws/my-recipes-chart-aws/templates/backend-service.yaml b/aws/my-recipes-chart-aws/templates/backend-service.yaml deleted file mode 100644 index 6608df6..0000000 --- a/aws/my-recipes-chart-aws/templates/backend-service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-{{ .Values.backend.name }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.backend.name }} - component: backend -spec: - type: {{ .Values.backend.service.type }} - ports: - - port: {{ .Values.backend.service.port }} - targetPort: {{ .Values.backend.service.targetPort }} - protocol: TCP - name: http - selector: - app: {{ .Release.Name }}-{{ .Values.backend.name }} diff --git a/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml b/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml deleted file mode 100644 index 5a7c4da..0000000 --- a/aws/my-recipes-chart-aws/templates/db-migration-configmap.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-db-migration - namespace: {{ .Values.global.namespace }} -data: - migrate.sql: | - -- Add made_by column to recipes if it doesn't exist - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'recipes' AND column_name = 'made_by' - ) THEN - ALTER TABLE recipes ADD COLUMN made_by TEXT; - END IF; - END $$; - - -- Create index if it doesn't exist - CREATE INDEX IF NOT EXISTS idx_recipes_made_by ON recipes (made_by); - - -- Add is_admin column to users if it doesn't exist - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'is_admin' - ) THEN - ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; - END IF; - END $$; - - -- Add auth_provider column to users if it doesn't exist - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'auth_provider' - ) THEN - ALTER TABLE users ADD COLUMN auth_provider TEXT DEFAULT 'local'; - END IF; - END $$; - - -- Verify recipes schema - SELECT column_name, data_type - FROM information_schema.columns - WHERE table_name = 'recipes' - ORDER BY ordinal_position; - - -- Verify users schema - SELECT column_name, data_type - FROM information_schema.columns - WHERE table_name = 'users' - ORDER BY ordinal_position; diff --git a/aws/my-recipes-chart-aws/templates/db-migration-job.yaml b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml deleted file mode 100644 index f153342..0000000 --- a/aws/my-recipes-chart-aws/templates/db-migration-job.yaml +++ /dev/null @@ -1,69 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: {{ .Release.Name }}-db-migration-{{ .Release.Revision }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-db-migration - component: migration - annotations: - "helm.sh/hook": post-upgrade,post-install - "helm.sh/hook-weight": "5" - "helm.sh/hook-delete-policy": before-hook-creation -spec: - ttlSecondsAfterFinished: 300 - template: - metadata: - labels: - app: {{ .Release.Name }}-db-migration - spec: - restartPolicy: Never - containers: - - name: migrate - image: postgres:16-alpine - command: - - /bin/sh - - -c - - | - echo "Waiting for database to be ready..." - until pg_isready -h $DB_HOST -U $DB_USER; do - echo "Database not ready, waiting..." - sleep 2 - done - echo "Database is ready, running migration..." - PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql - echo "Migration completed successfully" - env: - - name: DB_HOST - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_HOST - - name: DB_PORT - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PORT - - name: DB_NAME - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_NAME - - name: DB_USER - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_USER - - name: DB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-db-credentials - key: DB_PASSWORD - volumeMounts: - - name: migration-script - mountPath: /migration - volumes: - - name: migration-script - configMap: - name: {{ .Release.Name }}-db-migration - diff --git a/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml b/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml deleted file mode 100644 index c6ee6dc..0000000 --- a/aws/my-recipes-chart-aws/templates/db-schema-configmap.yaml +++ /dev/null @@ -1,134 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ .Release.Name }}-db-schema - namespace: {{ .Values.global.namespace }} -data: - schema.sql: | - -- Create users table - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - email TEXT UNIQUE NOT NULL, - password_hash TEXT NOT NULL, - first_name TEXT, - last_name TEXT, - display_name TEXT NOT NULL, - is_admin BOOLEAN DEFAULT FALSE, - auth_provider TEXT DEFAULT 'local', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - CREATE INDEX IF NOT EXISTS idx_users_username ON users (username); - CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); - - -- Create recipes table (matching backend schema with TEXT[] arrays) - CREATE TABLE IF NOT EXISTS recipes ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack - time_minutes INTEGER NOT NULL, - tags TEXT[] NOT NULL DEFAULT '{}', -- {"מהיר", "בריא"} - ingredients TEXT[] NOT NULL DEFAULT '{}', -- {"ביצה", "עגבניה", "מלח"} - steps TEXT[] NOT NULL DEFAULT '{}', -- {"לחתוך", "לבשל", ...} - image TEXT, -- Base64-encoded image or image URL - made_by TEXT, -- Person who created this recipe version - user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Recipe owner - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Indexes for filters - 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_user_id - ON recipes (user_id); - - -- Add new columns to existing users table - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'first_name' - ) THEN - ALTER TABLE users ADD COLUMN first_name TEXT; - END IF; - END $$; - - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'last_name' - ) THEN - ALTER TABLE users ADD COLUMN last_name TEXT; - END IF; - END $$; - - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'display_name' - ) THEN - ALTER TABLE users ADD COLUMN display_name TEXT; - -- Set display_name to username for existing users - UPDATE users SET display_name = username WHERE display_name IS NULL; - ALTER TABLE users ALTER COLUMN display_name SET NOT NULL; - END IF; - END $$; - - DO $$ - BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'is_admin' - ) THEN - ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; - END IF; - END $$; - - -- Create grocery lists table - CREATE TABLE IF NOT EXISTS grocery_lists ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - items TEXT[] NOT NULL DEFAULT '{}', - owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - is_pinned BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - -- Create grocery list shares table - CREATE TABLE IF NOT EXISTS grocery_list_shares ( - id SERIAL PRIMARY KEY, - list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, - shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - can_edit BOOLEAN DEFAULT FALSE, - shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(list_id, shared_with_user_id) - ); - - CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); - CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); - CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); - - -- Create notifications table - CREATE TABLE IF NOT EXISTS notifications ( - id SERIAL PRIMARY KEY, - user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, - type TEXT NOT NULL, - message TEXT NOT NULL, - related_id INTEGER, - is_read BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ); - - CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); - CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/my-recipes-chart-aws/templates/db-secret.yaml b/aws/my-recipes-chart-aws/templates/db-secret.yaml deleted file mode 100644 index aef2e21..0000000 --- a/aws/my-recipes-chart-aws/templates/db-secret.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ .Release.Name }}-db-credentials - namespace: {{ .Values.global.namespace }} -type: Opaque -stringData: - {{- if .Values.database }} - # External database (e.g., AWS RDS) - DB_HOST: {{ .Values.database.host | quote }} - DB_PORT: {{ .Values.database.port | quote }} - DB_NAME: {{ .Values.database.name | quote }} - DB_USER: {{ .Values.database.user | quote }} - DB_PASSWORD: {{ .Values.database.password | quote }} - {{- else }} - # In-cluster PostgreSQL - DB_HOST: {{ printf "%s-%s-headless.%s.svc.cluster.local" .Release.Name .Values.postgres.name .Values.global.namespace }} - DB_PORT: "{{ .Values.postgres.port }}" - DB_NAME: {{ .Values.postgres.database | quote }} - DB_USER: {{ .Values.postgres.user | quote }} - DB_PASSWORD: {{ .Values.postgres.password | quote }} - {{- end }} - diff --git a/aws/my-recipes-chart-aws/templates/db-service.yaml b/aws/my-recipes-chart-aws/templates/db-service.yaml deleted file mode 100644 index c4704d3..0000000 --- a/aws/my-recipes-chart-aws/templates/db-service.yaml +++ /dev/null @@ -1,39 +0,0 @@ -{{- if not .Values.database }} -{{- /* Only deploy in-cluster PostgreSQL services if external database is not configured */ -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - component: database -spec: - clusterIP: None - selector: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - ports: - - name: postgres - port: {{ .Values.postgres.port }} - targetPort: {{ .Values.postgres.port }} - protocol: TCP ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-{{ .Values.postgres.name }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - component: database -spec: - type: {{ .Values.postgres.service.type }} - selector: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - ports: - - name: postgres - port: {{ .Values.postgres.service.port }} - targetPort: {{ .Values.postgres.port }} - protocol: TCP -{{- end }} - diff --git a/aws/my-recipes-chart-aws/templates/db-statefulset.yaml b/aws/my-recipes-chart-aws/templates/db-statefulset.yaml deleted file mode 100644 index 93af59a..0000000 --- a/aws/my-recipes-chart-aws/templates/db-statefulset.yaml +++ /dev/null @@ -1,89 +0,0 @@ -{{- if not .Values.database }} -{{- /* Only deploy in-cluster PostgreSQL if external database is not configured */ -}} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ .Release.Name }}-{{ .Values.postgres.name }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - component: database -spec: - serviceName: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless - replicas: 1 - selector: - matchLabels: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - template: - metadata: - labels: - app: {{ .Release.Name }}-{{ .Values.postgres.name }} - component: database - spec: - containers: - - name: postgres - image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" - imagePullPolicy: {{ .Values.postgres.image.pullPolicy }} - ports: - - containerPort: {{ .Values.postgres.port }} - name: postgres - protocol: TCP - env: - - name: POSTGRES_USER - value: {{ .Values.postgres.user | quote }} - - name: POSTGRES_PASSWORD - value: {{ .Values.postgres.password | quote }} - - name: POSTGRES_DB - value: {{ .Values.postgres.database | quote }} - - name: PGDATA - value: /var/lib/postgresql/data/pgdata - volumeMounts: - - name: data - mountPath: /var/lib/postgresql/data - - name: init-sql - mountPath: /docker-entrypoint-initdb.d - livenessProbe: - exec: - command: - - /bin/sh - - -c - - pg_isready -U {{ .Values.postgres.user }} - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - exec: - command: - - /bin/sh - - -c - - pg_isready -U {{ .Values.postgres.user }} - initialDelaySeconds: 10 - periodSeconds: 5 - timeoutSeconds: 3 - failureThreshold: 2 - resources: - requests: - cpu: {{ .Values.postgres.resources.requests.cpu }} - memory: {{ .Values.postgres.resources.requests.memory }} - limits: - cpu: {{ .Values.postgres.resources.limits.cpu }} - memory: {{ .Values.postgres.resources.limits.memory }} - volumes: - - name: init-sql - configMap: - name: {{ .Release.Name }}-db-schema - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - {{ .Values.postgres.persistence.accessMode }} - resources: - requests: - storage: {{ .Values.postgres.persistence.size }} - {{- if .Values.postgres.persistence.storageClass }} - storageClassName: {{ .Values.postgres.persistence.storageClass | quote }} - {{- end }} -{{- end }} - diff --git a/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml b/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml deleted file mode 100644 index 8973e53..0000000 --- a/aws/my-recipes-chart-aws/templates/frontend-deployment.yaml +++ /dev/null @@ -1,57 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }}-{{ .Values.frontend.name }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.frontend.name }} - component: frontend -spec: - replicas: {{ .Values.frontend.replicaCount }} - selector: - matchLabels: - app: {{ .Release.Name }}-{{ .Values.frontend.name }} - template: - metadata: - labels: - app: {{ .Release.Name }}-{{ .Values.frontend.name }} - component: frontend - spec: - containers: - - name: {{ .Values.frontend.name }} - image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" - imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} - ports: - - containerPort: {{ .Values.frontend.service.targetPort }} - name: http - protocol: TCP - {{- with .Values.frontend.env }} - env: - {{- range $key, $value := . }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} - {{- end }} - livenessProbe: - httpGet: - path: / - port: http - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - httpGet: - path: / - port: http - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 3 - failureThreshold: 2 - resources: - requests: - cpu: {{ .Values.frontend.resources.requests.cpu }} - memory: {{ .Values.frontend.resources.requests.memory }} - limits: - cpu: {{ .Values.frontend.resources.limits.cpu }} - memory: {{ .Values.frontend.resources.limits.memory }} diff --git a/aws/my-recipes-chart-aws/templates/frontend-service.yaml b/aws/my-recipes-chart-aws/templates/frontend-service.yaml deleted file mode 100644 index 9427830..0000000 --- a/aws/my-recipes-chart-aws/templates/frontend-service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }}-{{ .Values.frontend.name }} - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-{{ .Values.frontend.name }} - component: frontend -spec: - type: {{ .Values.frontend.service.type }} - ports: - - port: {{ .Values.frontend.service.port }} - targetPort: {{ .Values.frontend.service.targetPort }} - protocol: TCP - name: http - selector: - app: {{ .Release.Name }}-{{ .Values.frontend.name }} diff --git a/aws/my-recipes-chart-aws/templates/ingress.yaml b/aws/my-recipes-chart-aws/templates/ingress.yaml deleted file mode 100644 index d106c59..0000000 --- a/aws/my-recipes-chart-aws/templates/ingress.yaml +++ /dev/null @@ -1,89 +0,0 @@ -{{- if .Values.frontend.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ .Release.Name }}-frontend - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-frontend - component: frontend - {{- with .Values.frontend.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.frontend.ingress.className }} - ingressClassName: {{ .Values.frontend.ingress.className }} - {{- end }} - rules: - {{- range .Values.frontend.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ .pathType }} - backend: - service: - name: {{ $.Release.Name }}-{{ $.Values.frontend.name }} - port: - number: {{ $.Values.frontend.service.port }} - {{- end }} - {{- end }} - {{- if .Values.frontend.ingress.tls }} - tls: - {{- range .Values.frontend.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} -{{- end }} - ---- - -{{- if .Values.backend.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ .Release.Name }}-backend - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-backend - component: backend - {{- with .Values.backend.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.backend.ingress.className }} - ingressClassName: {{ .Values.backend.ingress.className }} - {{- end }} - rules: - {{- range .Values.backend.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ .path }} - pathType: {{ .pathType }} - backend: - service: - name: {{ $.Release.Name }}-{{ $.Values.backend.name }} - port: - number: {{ $.Values.backend.service.port }} - {{- end }} - {{- end }} - {{- if .Values.backend.ingress.tls }} - tls: - {{- range .Values.backend.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} -{{- end }} diff --git a/aws/my-recipes-chart-aws/values.yaml b/aws/my-recipes-chart-aws/values.yaml deleted file mode 100644 index 31d7804..0000000 --- a/aws/my-recipes-chart-aws/values.yaml +++ /dev/null @@ -1,182 +0,0 @@ -global: - namespace: my-apps - imagePullSecrets: [] - -# Backend configuration -backend: - name: backend - replicaCount: 2 - image: - repository: harbor.dvirlabs.com/my-apps/my-recipes-backend - pullPolicy: IfNotPresent - tag: "latest" - - service: - type: ClusterIP - port: 8000 - targetPort: 8000 - - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi - - env: - PYTHONUNBUFFERED: "1" - - # Secrets are created in db-secret.yaml - # These are passed via envFrom secretRef - - ingress: - enabled: true - className: "alb" - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' - alb.ingress.kubernetes.io/certificate-arn: "" # Set in project-specific values - hosts: - - host: api-my-recipes.dvirlabs.com - paths: - - path: / - pathType: Prefix - tls: - - secretName: api-my-recipes-tls - hosts: - - api-my-recipes.dvirlabs.com - -# Frontend configuration -frontend: - name: frontend - replicaCount: 2 - image: - repository: harbor.dvirlabs.com/my-apps/my-recipes-frontend - pullPolicy: IfNotPresent - tag: "latest" - - service: - type: ClusterIP - port: 80 - targetPort: 80 - - env: - API_BASE: "https://api-my-recipes.dvirlabs.com" - - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 200m - memory: 256Mi - - ingress: - enabled: true - className: "alb" - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' - alb.ingress.kubernetes.io/certificate-arn: "" # Set in project-specific values - hosts: - - host: my-recipes.dvirlabs.com - paths: - - path: / - pathType: Prefix - tls: - - secretName: my-recipes-tls - hosts: - - my-recipes.dvirlabs.com - externalUrl: "https://my-recipes.dvirlabs.com" - -# PostgreSQL configuration -postgres: - name: db - image: - repository: postgres - tag: "16-alpine" - pullPolicy: IfNotPresent - - user: recipes_user - password: recipes_password - database: recipes_db - port: 5432 - - service: - type: ClusterIP - port: 5432 - targetPort: 5432 - - persistence: - enabled: true - accessMode: ReadWriteOnce - storageClass: "nfs-client" - size: 10Gi - - resources: - requests: - cpu: 100m - memory: 256Mi - limits: - cpu: 1000m - memory: 1Gi - -# OAuth Configuration -oauth: - google: - clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" - clientSecret: "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" - redirectUri: "https://api-my-recipes.dvirlabs.com/auth/google/callback" - - azure: - clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" - clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" - tenantId: "consumers" - redirectUri: "https://api-my-recipes.dvirlabs.com/auth/azure/callback" - -# Email Configuration -email: - smtpHost: "smtp.gmail.com" - smtpPort: "587" - smtpUser: "dvirlabs@gmail.com" - smtpPassword: "agaanrhbbazbdytv" - smtpFrom: "dvirlabs@gmail.com" - -# S3 Backup Configuration -s3: - endpoint: "https://s3.amazonaws.com" # Can be overridden for specific regions - accessKey: "" # Set this in project-specific values.yaml - secretKey: "" # Set this in project-specific values.yaml - bucketName: "" # Set this in project-specific values.yaml - region: "us-east-1" # Set this in project-specific values.yaml - backupInterval: "weekly" # Options: test (1 min), daily, weekly - -# Admin User Configuration -admin: - username: "admin" - email: "admin@example.com" - password: "admin123" # Change this in production! - firstName: "Admin" - lastName: "User" - displayName: "Admin User" - -# Ingress configuration -ingress: - enabled: false # Individual frontend/backend ingress resources handle routing instead - className: "nginx" - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" - hosts: - - host: my-recipes.dvirlabs.com - paths: - - path: / - pathType: Prefix - backend: frontend - tls: - - secretName: recipes-tls - hosts: - - my-recipes.dvirlabs.com - diff --git a/aws/values.yaml b/aws/values.yaml deleted file mode 100644 index e526c3d..0000000 --- a/aws/values.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# Project-specific values for AWS EKS deployment -# This file overrides the base values in my-recipes-chart/values.yaml - -global: - namespace: my-apps - -# Backend configuration -backend: - replicaCount: 2 - image: - repository: 430842105273.dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend # Update with your ECR repository - tag: "latest" - - ingress: - className: "alb" - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' - # Add your ACM certificate ARN below if you have one - # alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..." - hosts: - - host: api-my-recipes.aws-dvirlabs.com - paths: - - path: / - pathType: Prefix - -# Frontend configuration -frontend: - replicaCount: 2 - image: - repository: 430842105273.dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend # Update with your ECR repository - tag: "latest" - - env: - API_BASE: "https://api-my-recipes.aws-dvirlabs.com" - - ingress: - className: "alb" - annotations: - alb.ingress.kubernetes.io/scheme: internet-facing - alb.ingress.kubernetes.io/target-type: ip - alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' - # Add your ACM certificate ARN below if you have one - # alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..." - hosts: - - host: my-recipes.aws-dvirlabs.com - paths: - - path: / - pathType: Prefix - - externalUrl: "https://my-recipes.aws-dvirlabs.com" - -# PostgreSQL configuration -postgres: - # For AWS RDS, set this to use external database - # Leave enabled: true to use in-cluster database - enabled: false # Set to false if using RDS - - # If using RDS, these values are ignored but kept for reference - persistence: - storageClass: "gp3" # EKS default storage class - size: 20Gi - -# OAuth Configuration -oauth: - google: - clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" - clientSecret: "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" - redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/google/callback" - - azure: - clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" - clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" - tenantId: "consumers" - redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/azure/callback" - -# Email Configuration -email: - smtpHost: "smtp.gmail.com" - smtpPort: "587" - smtpUser: "dvirlabs@gmail.com" - smtpPassword: "agaanrhbbazbdytv" - smtpFrom: "dvirlabs@gmail.com" - -# S3 Backup Configuration for AWS -s3: - endpoint: "https://s3.eu-central-1.amazonaws.com" # Update with your region - accessKey: "AKIAXXXXXXXXXXXXXXXX" # Replace with your AWS Access Key - secretKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with your AWS Secret Key - bucketName: "my-recipes-backups" # Update with your S3 bucket name - region: "eu-central-1" # Update with your region - backupInterval: "weekly" - -# Admin User Configuration -admin: - username: "admin" - email: "dvirlabs@gmail.com" - password: "AdminPassword123!" # Change this after first login! - firstName: "Dvir" - lastName: "Admin" - displayName: "Dvir Admin" - -# Database connection for AWS RDS (used when postgres.enabled: false) -database: - host: "my-recipes-rds.chw4omcguqv7.eu-central-1.rds.amazonaws.com" - port: "5432" - name: "recipes_db" - user: "recipes_user" - password: "recipes_password" # Store securely in AWS Secrets Manager in production From 16938f42bf865b985243a68ea11a3df420ecbd7d Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:55:23 +0200 Subject: [PATCH 07/11] Update schema.sql --- aws/final-app/my-recipes-chart-aws/Chart.yaml | 14 ++ .../add-missing-tables-configmap.yaml | 45 +++++ .../templates/add-missing-tables-job.yaml | 49 +++++ .../templates/admin-init-configmap.yaml | 99 ++++++++++ .../templates/admin-init-job.yaml | 75 ++++++++ .../templates/app-secrets.yaml | 35 ++++ .../templates/backend-deployment.yaml | 119 ++++++++++++ .../templates/backend-service.yaml | 17 ++ .../templates/db-migration-configmap.yaml | 54 ++++++ .../templates/db-migration-job.yaml | 69 +++++++ .../templates/db-schema-configmap.yaml | 134 +++++++++++++ .../templates/db-secret.yaml | 23 +++ .../templates/db-service.yaml | 39 ++++ .../templates/db-statefulset.yaml | 89 +++++++++ .../templates/frontend-deployment.yaml | 57 ++++++ .../templates/frontend-service.yaml | 17 ++ .../templates/ingress.yaml | 89 +++++++++ .../my-recipes-chart-aws/values.yaml | 182 ++++++++++++++++++ aws/final-app/values.yaml | 110 +++++++++++ 19 files changed, 1316 insertions(+) create mode 100644 aws/final-app/my-recipes-chart-aws/Chart.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-job.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/admin-init-configmap.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/admin-init-job.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/app-secrets.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/backend-deployment.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/backend-service.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/db-migration-configmap.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/db-migration-job.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/db-schema-configmap.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/db-secret.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/db-service.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/db-statefulset.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/frontend-deployment.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/frontend-service.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/templates/ingress.yaml create mode 100644 aws/final-app/my-recipes-chart-aws/values.yaml create mode 100644 aws/final-app/values.yaml diff --git a/aws/final-app/my-recipes-chart-aws/Chart.yaml b/aws/final-app/my-recipes-chart-aws/Chart.yaml new file mode 100644 index 0000000..86c815b --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: my-recipes +description: Complete recipe management application with PostgreSQL, FastAPI backend, and React frontend +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - recipes + - fastapi + - react + - postgresql +maintainers: + - name: Development Team + diff --git a/aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml b/aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml new file mode 100644 index 0000000..d15cad9 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-configmap.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-add-missing-tables + namespace: {{ .Values.global.namespace }} +data: + add-tables.sql: | + -- Create grocery lists table + CREATE TABLE IF NOT EXISTS grocery_lists ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + items TEXT[] NOT NULL DEFAULT '{}', + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_pinned BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create grocery list shares table + CREATE TABLE IF NOT EXISTS grocery_list_shares ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, + shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + can_edit BOOLEAN DEFAULT FALSE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(list_id, shared_with_user_id) + ); + + CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); + + -- Create notifications table + CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type TEXT NOT NULL, + message TEXT NOT NULL, + related_id INTEGER, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); + CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-job.yaml b/aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-job.yaml new file mode 100644 index 0000000..e0ca1bc --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/add-missing-tables-job.yaml @@ -0,0 +1,49 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-add-missing-tables + namespace: {{ .Values.global.namespace }} + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-weight": "6" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + template: + spec: + restartPolicy: Never + containers: + - name: add-tables + image: postgres:16-alpine + env: + - name: PGHOST + value: {{ .Release.Name }}-db + - name: PGPORT + value: "{{ .Values.postgres.port }}" + - name: PGDATABASE + value: {{ .Values.postgres.database }} + - name: PGUSER + value: {{ .Values.postgres.user }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + command: + - sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $PGHOST -p $PGPORT -U $PGUSER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database ready, adding missing tables..." + psql -v ON_ERROR_STOP=1 -f /sql/add-tables.sql + echo "Tables added successfully!" + volumeMounts: + - name: sql + mountPath: /sql + volumes: + - name: sql + configMap: + name: {{ .Release.Name }}-add-missing-tables diff --git a/aws/final-app/my-recipes-chart-aws/templates/admin-init-configmap.yaml b/aws/final-app/my-recipes-chart-aws/templates/admin-init-configmap.yaml new file mode 100644 index 0000000..7bda3cd --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/admin-init-configmap.yaml @@ -0,0 +1,99 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-admin-init + namespace: {{ .Values.global.namespace }} +data: + create-admin.py: | + #!/usr/bin/env python3 + import os + import sys + import psycopg2 + import bcrypt + from time import sleep + + def wait_for_db(): + """Wait for database to be ready""" + max_retries = 30 + retry_count = 0 + + while retry_count < max_retries: + try: + conn = psycopg2.connect( + host=os.environ['DB_HOST'], + port=os.environ['DB_PORT'], + database=os.environ['DB_NAME'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'] + ) + conn.close() + print("✓ Database is ready") + return True + except Exception as e: + retry_count += 1 + print(f"Waiting for database... ({retry_count}/{max_retries})") + sleep(2) + + print("✗ Database connection timeout") + return False + + def create_admin_user(): + """Create admin user if not exists""" + try: + # Hash the password + password = os.environ.get('ADMIN_PASSWORD', 'admin123') + password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + # Connect to database + conn = psycopg2.connect( + host=os.environ['DB_HOST'], + port=os.environ['DB_PORT'], + database=os.environ['DB_NAME'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'] + ) + cur = conn.cursor() + + # Insert admin user + cur.execute(""" + INSERT INTO users (username, email, password_hash, first_name, last_name, display_name, is_admin) + VALUES (%s, %s, %s, %s, %s, %s, %s) + ON CONFLICT (username) DO UPDATE SET + email = EXCLUDED.email, + password_hash = EXCLUDED.password_hash, + first_name = EXCLUDED.first_name, + last_name = EXCLUDED.last_name, + display_name = EXCLUDED.display_name, + is_admin = EXCLUDED.is_admin + """, ( + os.environ.get('ADMIN_USERNAME', 'admin'), + os.environ.get('ADMIN_EMAIL', 'admin@myrecipes.local'), + password_hash, + os.environ.get('ADMIN_FIRST_NAME', 'Admin'), + os.environ.get('ADMIN_LAST_NAME', 'User'), + os.environ.get('ADMIN_DISPLAY_NAME', 'מנהל'), + True + )) + + conn.commit() + cur.close() + conn.close() + + print(f"✓ Admin user '{os.environ.get('ADMIN_USERNAME', 'admin')}' created/updated successfully") + return True + + except Exception as e: + print(f"✗ Error creating admin user: {e}") + return False + + if __name__ == "__main__": + print("Starting admin user initialization...") + + if not wait_for_db(): + sys.exit(1) + + if not create_admin_user(): + sys.exit(1) + + print("✓ Admin user initialization completed") + sys.exit(0) diff --git a/aws/final-app/my-recipes-chart-aws/templates/admin-init-job.yaml b/aws/final-app/my-recipes-chart-aws/templates/admin-init-job.yaml new file mode 100644 index 0000000..511ec36 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/admin-init-job.yaml @@ -0,0 +1,75 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-admin-init-{{ .Release.Revision }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-admin-init + component: init + annotations: + "helm.sh/hook": post-install,post-upgrade + "helm.sh/hook-weight": "10" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + app: {{ .Release.Name }}-admin-init + spec: + restartPolicy: Never + containers: + - name: admin-init + image: python:3.12-slim + command: + - /bin/sh + - -c + - | + pip install --no-cache-dir psycopg2-binary bcrypt > /dev/null 2>&1 + python3 /scripts/create-admin.py + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + - name: ADMIN_USERNAME + value: {{ .Values.admin.username | quote }} + - name: ADMIN_EMAIL + value: {{ .Values.admin.email | quote }} + - name: ADMIN_PASSWORD + value: {{ .Values.admin.password | quote }} + - name: ADMIN_FIRST_NAME + value: {{ .Values.admin.firstName | quote }} + - name: ADMIN_LAST_NAME + value: {{ .Values.admin.lastName | quote }} + - name: ADMIN_DISPLAY_NAME + value: {{ .Values.admin.displayName | quote }} + volumeMounts: + - name: init-script + mountPath: /scripts + volumes: + - name: init-script + configMap: + name: {{ .Release.Name }}-admin-init + defaultMode: 0755 diff --git a/aws/final-app/my-recipes-chart-aws/templates/app-secrets.yaml b/aws/final-app/my-recipes-chart-aws/templates/app-secrets.yaml new file mode 100644 index 0000000..93285f3 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/app-secrets.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-app-secrets + namespace: {{ .Values.global.namespace }} +type: Opaque +stringData: + # Google OAuth + GOOGLE_CLIENT_ID: {{ .Values.oauth.google.clientId | quote }} + GOOGLE_CLIENT_SECRET: {{ .Values.oauth.google.clientSecret | quote }} + GOOGLE_REDIRECT_URI: {{ .Values.oauth.google.redirectUri | quote }} + + # Microsoft Entra ID (Azure AD) OAuth + AZURE_CLIENT_ID: {{ .Values.oauth.azure.clientId | quote }} + AZURE_CLIENT_SECRET: {{ .Values.oauth.azure.clientSecret | quote }} + AZURE_TENANT_ID: {{ .Values.oauth.azure.tenantId | quote }} + AZURE_REDIRECT_URI: {{ .Values.oauth.azure.redirectUri | quote }} + + # Email Configuration + SMTP_HOST: {{ .Values.email.smtpHost | quote }} + SMTP_PORT: {{ .Values.email.smtpPort | quote }} + SMTP_USER: {{ .Values.email.smtpUser | quote }} + SMTP_PASSWORD: {{ .Values.email.smtpPassword | quote }} + SMTP_FROM: {{ .Values.email.smtpFrom | quote }} + + # Frontend URL for redirects + FRONTEND_URL: {{ .Values.frontend.externalUrl | quote }} + + # S3 Backup Configuration + S3_ENDPOINT: {{ .Values.s3.endpoint | quote }} + S3_ACCESS_KEY: {{ .Values.s3.accessKey | quote }} + S3_SECRET_KEY: {{ .Values.s3.secretKey | quote }} + S3_BUCKET_NAME: {{ .Values.s3.bucketName | quote }} + S3_REGION: {{ .Values.s3.region | quote }} + BACKUP_INTERVAL: {{ .Values.s3.backupInterval | quote }} diff --git a/aws/final-app/my-recipes-chart-aws/templates/backend-deployment.yaml b/aws/final-app/my-recipes-chart-aws/templates/backend-deployment.yaml new file mode 100644 index 0000000..b7379bd --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/backend-deployment.yaml @@ -0,0 +1,119 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + replicas: {{ .Values.backend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend + spec: + initContainers: + - name: db-migration + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $DB_HOST -U $DB_USER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database is ready, running migration..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql + echo "Migration completed successfully" + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + volumeMounts: + - name: migration-script + mountPath: /migration + containers: + - name: {{ .Values.backend.name }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.service.targetPort }} + name: http + protocol: TCP + env: + {{- if .Values.backend.env }} + {{- range $key, $value := .Values.backend.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-db-credentials + - secretRef: + name: {{ .Release.Name }}-app-secrets + startupProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 30 + livenessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /docs + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.backend.resources.requests.cpu }} + memory: {{ .Values.backend.resources.requests.memory }} + limits: + cpu: {{ .Values.backend.resources.limits.cpu }} + memory: {{ .Values.backend.resources.limits.memory }} + volumes: + - name: migration-script + configMap: + name: {{ .Release.Name }}-db-migration + diff --git a/aws/final-app/my-recipes-chart-aws/templates/backend-service.yaml b/aws/final-app/my-recipes-chart-aws/templates/backend-service.yaml new file mode 100644 index 0000000..6608df6 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/backend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.backend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.backend.name }} + component: backend +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: {{ .Values.backend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.backend.name }} diff --git a/aws/final-app/my-recipes-chart-aws/templates/db-migration-configmap.yaml b/aws/final-app/my-recipes-chart-aws/templates/db-migration-configmap.yaml new file mode 100644 index 0000000..5a7c4da --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/db-migration-configmap.yaml @@ -0,0 +1,54 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-migration + namespace: {{ .Values.global.namespace }} +data: + migrate.sql: | + -- Add made_by column to recipes if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'recipes' AND column_name = 'made_by' + ) THEN + ALTER TABLE recipes ADD COLUMN made_by TEXT; + END IF; + END $$; + + -- Create index if it doesn't exist + CREATE INDEX IF NOT EXISTS idx_recipes_made_by ON recipes (made_by); + + -- Add is_admin column to users if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_admin' + ) THEN + ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; + END IF; + END $$; + + -- Add auth_provider column to users if it doesn't exist + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'auth_provider' + ) THEN + ALTER TABLE users ADD COLUMN auth_provider TEXT DEFAULT 'local'; + END IF; + END $$; + + -- Verify recipes schema + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'recipes' + ORDER BY ordinal_position; + + -- Verify users schema + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = 'users' + ORDER BY ordinal_position; diff --git a/aws/final-app/my-recipes-chart-aws/templates/db-migration-job.yaml b/aws/final-app/my-recipes-chart-aws/templates/db-migration-job.yaml new file mode 100644 index 0000000..f153342 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/db-migration-job.yaml @@ -0,0 +1,69 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-db-migration-{{ .Release.Revision }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-db-migration + component: migration + annotations: + "helm.sh/hook": post-upgrade,post-install + "helm.sh/hook-weight": "5" + "helm.sh/hook-delete-policy": before-hook-creation +spec: + ttlSecondsAfterFinished: 300 + template: + metadata: + labels: + app: {{ .Release.Name }}-db-migration + spec: + restartPolicy: Never + containers: + - name: migrate + image: postgres:16-alpine + command: + - /bin/sh + - -c + - | + echo "Waiting for database to be ready..." + until pg_isready -h $DB_HOST -U $DB_USER; do + echo "Database not ready, waiting..." + sleep 2 + done + echo "Database is ready, running migration..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql + echo "Migration completed successfully" + env: + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_HOST + - name: DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PORT + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_NAME + - name: DB_USER + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_USER + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-db-credentials + key: DB_PASSWORD + volumeMounts: + - name: migration-script + mountPath: /migration + volumes: + - name: migration-script + configMap: + name: {{ .Release.Name }}-db-migration + diff --git a/aws/final-app/my-recipes-chart-aws/templates/db-schema-configmap.yaml b/aws/final-app/my-recipes-chart-aws/templates/db-schema-configmap.yaml new file mode 100644 index 0000000..c6ee6dc --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/db-schema-configmap.yaml @@ -0,0 +1,134 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-db-schema + namespace: {{ .Values.global.namespace }} +data: + schema.sql: | + -- Create users table + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + first_name TEXT, + last_name TEXT, + display_name TEXT NOT NULL, + is_admin BOOLEAN DEFAULT FALSE, + auth_provider TEXT DEFAULT 'local', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_users_username ON users (username); + CREATE INDEX IF NOT EXISTS idx_users_email ON users (email); + + -- Create recipes table (matching backend schema with TEXT[] arrays) + CREATE TABLE IF NOT EXISTS recipes ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + meal_type TEXT NOT NULL, -- breakfast / lunch / dinner / snack + time_minutes INTEGER NOT NULL, + tags TEXT[] NOT NULL DEFAULT '{}', -- {"מהיר", "בריא"} + ingredients TEXT[] NOT NULL DEFAULT '{}', -- {"ביצה", "עגבניה", "מלח"} + steps TEXT[] NOT NULL DEFAULT '{}', -- {"לחתוך", "לבשל", ...} + image TEXT, -- Base64-encoded image or image URL + made_by TEXT, -- Person who created this recipe version + user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, -- Recipe owner + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Indexes for filters + 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_user_id + ON recipes (user_id); + + -- Add new columns to existing users table + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'first_name' + ) THEN + ALTER TABLE users ADD COLUMN first_name TEXT; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'last_name' + ) THEN + ALTER TABLE users ADD COLUMN last_name TEXT; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'display_name' + ) THEN + ALTER TABLE users ADD COLUMN display_name TEXT; + -- Set display_name to username for existing users + UPDATE users SET display_name = username WHERE display_name IS NULL; + ALTER TABLE users ALTER COLUMN display_name SET NOT NULL; + END IF; + END $$; + + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_admin' + ) THEN + ALTER TABLE users ADD COLUMN is_admin BOOLEAN DEFAULT FALSE; + END IF; + END $$; + + -- Create grocery lists table + CREATE TABLE IF NOT EXISTS grocery_lists ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + items TEXT[] NOT NULL DEFAULT '{}', + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_pinned BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Create grocery list shares table + CREATE TABLE IF NOT EXISTS grocery_list_shares ( + id SERIAL PRIMARY KEY, + list_id INTEGER NOT NULL REFERENCES grocery_lists(id) ON DELETE CASCADE, + shared_with_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + can_edit BOOLEAN DEFAULT FALSE, + shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(list_id, shared_with_user_id) + ); + + CREATE INDEX IF NOT EXISTS idx_grocery_lists_owner_id ON grocery_lists (owner_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_list_id ON grocery_list_shares (list_id); + CREATE INDEX IF NOT EXISTS idx_grocery_list_shares_user_id ON grocery_list_shares (shared_with_user_id); + + -- Create notifications table + CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + type TEXT NOT NULL, + message TEXT NOT NULL, + related_id INTEGER, + is_read BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON notifications (user_id); + CREATE INDEX IF NOT EXISTS idx_notifications_is_read ON notifications (is_read); diff --git a/aws/final-app/my-recipes-chart-aws/templates/db-secret.yaml b/aws/final-app/my-recipes-chart-aws/templates/db-secret.yaml new file mode 100644 index 0000000..aef2e21 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/db-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-db-credentials + namespace: {{ .Values.global.namespace }} +type: Opaque +stringData: + {{- if .Values.database }} + # External database (e.g., AWS RDS) + DB_HOST: {{ .Values.database.host | quote }} + DB_PORT: {{ .Values.database.port | quote }} + DB_NAME: {{ .Values.database.name | quote }} + DB_USER: {{ .Values.database.user | quote }} + DB_PASSWORD: {{ .Values.database.password | quote }} + {{- else }} + # In-cluster PostgreSQL + DB_HOST: {{ printf "%s-%s-headless.%s.svc.cluster.local" .Release.Name .Values.postgres.name .Values.global.namespace }} + DB_PORT: "{{ .Values.postgres.port }}" + DB_NAME: {{ .Values.postgres.database | quote }} + DB_USER: {{ .Values.postgres.user | quote }} + DB_PASSWORD: {{ .Values.postgres.password | quote }} + {{- end }} + diff --git a/aws/final-app/my-recipes-chart-aws/templates/db-service.yaml b/aws/final-app/my-recipes-chart-aws/templates/db-service.yaml new file mode 100644 index 0000000..c4704d3 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/db-service.yaml @@ -0,0 +1,39 @@ +{{- if not .Values.database }} +{{- /* Only deploy in-cluster PostgreSQL services if external database is not configured */ -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + clusterIP: None + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + type: {{ .Values.postgres.service.type }} + selector: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + ports: + - name: postgres + port: {{ .Values.postgres.service.port }} + targetPort: {{ .Values.postgres.port }} + protocol: TCP +{{- end }} + diff --git a/aws/final-app/my-recipes-chart-aws/templates/db-statefulset.yaml b/aws/final-app/my-recipes-chart-aws/templates/db-statefulset.yaml new file mode 100644 index 0000000..93af59a --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/db-statefulset.yaml @@ -0,0 +1,89 @@ +{{- if not .Values.database }} +{{- /* Only deploy in-cluster PostgreSQL if external database is not configured */ -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }}-{{ .Values.postgres.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database +spec: + serviceName: {{ .Release.Name }}-{{ .Values.postgres.name }}-headless + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.postgres.name }} + component: database + spec: + containers: + - name: postgres + image: "{{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}" + imagePullPolicy: {{ .Values.postgres.image.pullPolicy }} + ports: + - containerPort: {{ .Values.postgres.port }} + name: postgres + protocol: TCP + env: + - name: POSTGRES_USER + value: {{ .Values.postgres.user | quote }} + - name: POSTGRES_PASSWORD + value: {{ .Values.postgres.password | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgres.database | quote }} + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + - name: init-sql + mountPath: /docker-entrypoint-initdb.d + livenessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - pg_isready -U {{ .Values.postgres.user }} + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.postgres.resources.requests.cpu }} + memory: {{ .Values.postgres.resources.requests.memory }} + limits: + cpu: {{ .Values.postgres.resources.limits.cpu }} + memory: {{ .Values.postgres.resources.limits.memory }} + volumes: + - name: init-sql + configMap: + name: {{ .Release.Name }}-db-schema + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.postgres.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.postgres.persistence.size }} + {{- if .Values.postgres.persistence.storageClass }} + storageClassName: {{ .Values.postgres.persistence.storageClass | quote }} + {{- end }} +{{- end }} + diff --git a/aws/final-app/my-recipes-chart-aws/templates/frontend-deployment.yaml b/aws/final-app/my-recipes-chart-aws/templates/frontend-deployment.yaml new file mode 100644 index 0000000..8973e53 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/frontend-deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + replicas: {{ .Values.frontend.replicaCount }} + selector: + matchLabels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + template: + metadata: + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend + spec: + containers: + - name: {{ .Values.frontend.name }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.service.targetPort }} + name: http + protocol: TCP + {{- with .Values.frontend.env }} + env: + {{- range $key, $value := . }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 2 + resources: + requests: + cpu: {{ .Values.frontend.resources.requests.cpu }} + memory: {{ .Values.frontend.resources.requests.memory }} + limits: + cpu: {{ .Values.frontend.resources.limits.cpu }} + memory: {{ .Values.frontend.resources.limits.memory }} diff --git a/aws/final-app/my-recipes-chart-aws/templates/frontend-service.yaml b/aws/final-app/my-recipes-chart-aws/templates/frontend-service.yaml new file mode 100644 index 0000000..9427830 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-{{ .Values.frontend.name }} + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} + component: frontend +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: {{ .Values.frontend.service.targetPort }} + protocol: TCP + name: http + selector: + app: {{ .Release.Name }}-{{ .Values.frontend.name }} diff --git a/aws/final-app/my-recipes-chart-aws/templates/ingress.yaml b/aws/final-app/my-recipes-chart-aws/templates/ingress.yaml new file mode 100644 index 0000000..d106c59 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/templates/ingress.yaml @@ -0,0 +1,89 @@ +{{- if .Values.frontend.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-frontend + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-frontend + component: frontend + {{- with .Values.frontend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.frontend.ingress.className }} + ingressClassName: {{ .Values.frontend.ingress.className }} + {{- end }} + rules: + {{- range .Values.frontend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-{{ $.Values.frontend.name }} + port: + number: {{ $.Values.frontend.service.port }} + {{- end }} + {{- end }} + {{- if .Values.frontend.ingress.tls }} + tls: + {{- range .Values.frontend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} + +--- + +{{- if .Values.backend.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-backend + namespace: {{ .Values.global.namespace }} + labels: + app: {{ .Release.Name }}-backend + component: backend + {{- with .Values.backend.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.backend.ingress.className }} + ingressClassName: {{ .Values.backend.ingress.className }} + {{- end }} + rules: + {{- range .Values.backend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ $.Release.Name }}-{{ $.Values.backend.name }} + port: + number: {{ $.Values.backend.service.port }} + {{- end }} + {{- end }} + {{- if .Values.backend.ingress.tls }} + tls: + {{- range .Values.backend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{- end }} diff --git a/aws/final-app/my-recipes-chart-aws/values.yaml b/aws/final-app/my-recipes-chart-aws/values.yaml new file mode 100644 index 0000000..31d7804 --- /dev/null +++ b/aws/final-app/my-recipes-chart-aws/values.yaml @@ -0,0 +1,182 @@ +global: + namespace: my-apps + imagePullSecrets: [] + +# Backend configuration +backend: + name: backend + replicaCount: 2 + image: + repository: harbor.dvirlabs.com/my-apps/my-recipes-backend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 8000 + targetPort: 8000 + + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + + env: + PYTHONUNBUFFERED: "1" + + # Secrets are created in db-secret.yaml + # These are passed via envFrom secretRef + + ingress: + enabled: true + className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + alb.ingress.kubernetes.io/certificate-arn: "" # Set in project-specific values + hosts: + - host: api-my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: api-my-recipes-tls + hosts: + - api-my-recipes.dvirlabs.com + +# Frontend configuration +frontend: + name: frontend + replicaCount: 2 + image: + repository: harbor.dvirlabs.com/my-apps/my-recipes-frontend + pullPolicy: IfNotPresent + tag: "latest" + + service: + type: ClusterIP + port: 80 + targetPort: 80 + + env: + API_BASE: "https://api-my-recipes.dvirlabs.com" + + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + + ingress: + enabled: true + className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + alb.ingress.kubernetes.io/certificate-arn: "" # Set in project-specific values + hosts: + - host: my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: my-recipes-tls + hosts: + - my-recipes.dvirlabs.com + externalUrl: "https://my-recipes.dvirlabs.com" + +# PostgreSQL configuration +postgres: + name: db + image: + repository: postgres + tag: "16-alpine" + pullPolicy: IfNotPresent + + user: recipes_user + password: recipes_password + database: recipes_db + port: 5432 + + service: + type: ClusterIP + port: 5432 + targetPort: 5432 + + persistence: + enabled: true + accessMode: ReadWriteOnce + storageClass: "nfs-client" + size: 10Gi + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# OAuth Configuration +oauth: + google: + clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" + clientSecret: "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" + redirectUri: "https://api-my-recipes.dvirlabs.com/auth/google/callback" + + azure: + clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" + clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" + tenantId: "consumers" + redirectUri: "https://api-my-recipes.dvirlabs.com/auth/azure/callback" + +# Email Configuration +email: + smtpHost: "smtp.gmail.com" + smtpPort: "587" + smtpUser: "dvirlabs@gmail.com" + smtpPassword: "agaanrhbbazbdytv" + smtpFrom: "dvirlabs@gmail.com" + +# S3 Backup Configuration +s3: + endpoint: "https://s3.amazonaws.com" # Can be overridden for specific regions + accessKey: "" # Set this in project-specific values.yaml + secretKey: "" # Set this in project-specific values.yaml + bucketName: "" # Set this in project-specific values.yaml + region: "us-east-1" # Set this in project-specific values.yaml + backupInterval: "weekly" # Options: test (1 min), daily, weekly + +# Admin User Configuration +admin: + username: "admin" + email: "admin@example.com" + password: "admin123" # Change this in production! + firstName: "Admin" + lastName: "User" + displayName: "Admin User" + +# Ingress configuration +ingress: + enabled: false # Individual frontend/backend ingress resources handle routing instead + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: my-recipes.dvirlabs.com + paths: + - path: / + pathType: Prefix + backend: frontend + tls: + - secretName: recipes-tls + hosts: + - my-recipes.dvirlabs.com + diff --git a/aws/final-app/values.yaml b/aws/final-app/values.yaml new file mode 100644 index 0000000..e526c3d --- /dev/null +++ b/aws/final-app/values.yaml @@ -0,0 +1,110 @@ +# Project-specific values for AWS EKS deployment +# This file overrides the base values in my-recipes-chart/values.yaml + +global: + namespace: my-apps + +# Backend configuration +backend: + replicaCount: 2 + image: + repository: 430842105273.dkr.ecr.eu-central-1.amazonaws.com/my-recipes-backend # Update with your ECR repository + tag: "latest" + + ingress: + className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + # Add your ACM certificate ARN below if you have one + # alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..." + hosts: + - host: api-my-recipes.aws-dvirlabs.com + paths: + - path: / + pathType: Prefix + +# Frontend configuration +frontend: + replicaCount: 2 + image: + repository: 430842105273.dkr.ecr.eu-central-1.amazonaws.com/my-recipes-frontend # Update with your ECR repository + tag: "latest" + + env: + API_BASE: "https://api-my-recipes.aws-dvirlabs.com" + + ingress: + className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + # Add your ACM certificate ARN below if you have one + # alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:..." + hosts: + - host: my-recipes.aws-dvirlabs.com + paths: + - path: / + pathType: Prefix + + externalUrl: "https://my-recipes.aws-dvirlabs.com" + +# PostgreSQL configuration +postgres: + # For AWS RDS, set this to use external database + # Leave enabled: true to use in-cluster database + enabled: false # Set to false if using RDS + + # If using RDS, these values are ignored but kept for reference + persistence: + storageClass: "gp3" # EKS default storage class + size: 20Gi + +# OAuth Configuration +oauth: + google: + clientId: "143092846986-hsi59m0on2c9rb5qrdoejfceieao2ioc.apps.googleusercontent.com" + clientSecret: "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" + redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/google/callback" + + azure: + clientId: "db244cf5-eb11-4738-a2ea-5b0716c9ec0a" + clientSecret: "Zad8Q~qRBxaQq8up0lLXAq4pHzrVM2JFGFJhHaDp" + tenantId: "consumers" + redirectUri: "https://api-my-recipes.aws-dvirlabs.com/auth/azure/callback" + +# Email Configuration +email: + smtpHost: "smtp.gmail.com" + smtpPort: "587" + smtpUser: "dvirlabs@gmail.com" + smtpPassword: "agaanrhbbazbdytv" + smtpFrom: "dvirlabs@gmail.com" + +# S3 Backup Configuration for AWS +s3: + endpoint: "https://s3.eu-central-1.amazonaws.com" # Update with your region + accessKey: "AKIAXXXXXXXXXXXXXXXX" # Replace with your AWS Access Key + secretKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Replace with your AWS Secret Key + bucketName: "my-recipes-backups" # Update with your S3 bucket name + region: "eu-central-1" # Update with your region + backupInterval: "weekly" + +# Admin User Configuration +admin: + username: "admin" + email: "dvirlabs@gmail.com" + password: "AdminPassword123!" # Change this after first login! + firstName: "Dvir" + lastName: "Admin" + displayName: "Dvir Admin" + +# Database connection for AWS RDS (used when postgres.enabled: false) +database: + host: "my-recipes-rds.chw4omcguqv7.eu-central-1.rds.amazonaws.com" + port: "5432" + name: "recipes_db" + user: "recipes_user" + password: "recipes_password" # Store securely in AWS Secrets Manager in production From 0b9fd670b9c5e19bc5984cb2fe3e2d7a780f8b6e Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:59:34 +0200 Subject: [PATCH 08/11] Update scema.sql --- .../templates/db-migration-job.yaml | 10 +++- .../templates/ingress.yaml | 49 ++----------------- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/aws/my-recipes-chart-aws/templates/db-migration-job.yaml b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml index f153342..3cf1dbe 100644 --- a/aws/my-recipes-chart-aws/templates/db-migration-job.yaml +++ b/aws/my-recipes-chart-aws/templates/db-migration-job.yaml @@ -30,7 +30,9 @@ spec: echo "Database not ready, waiting..." sleep 2 done - echo "Database is ready, running migration..." + echo "Database is ready, applying schema..." + PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /schema/schema.sql + echo "Schema applied, running migrations..." PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -U $DB_USER -d $DB_NAME -f /migration/migrate.sql echo "Migration completed successfully" env: @@ -62,8 +64,12 @@ spec: volumeMounts: - name: migration-script mountPath: /migration + - name: schema-script + mountPath: /schema volumes: - name: migration-script configMap: name: {{ .Release.Name }}-db-migration - + - name: schema-script + configMap: + name: {{ .Release.Name }}-db-schema diff --git a/aws/my-recipes-chart-aws/templates/ingress.yaml b/aws/my-recipes-chart-aws/templates/ingress.yaml index d106c59..af6f83e 100644 --- a/aws/my-recipes-chart-aws/templates/ingress.yaml +++ b/aws/my-recipes-chart-aws/templates/ingress.yaml @@ -2,11 +2,10 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ .Release.Name }}-frontend + name: {{ .Release.Name }} namespace: {{ .Values.global.namespace }} labels: - app: {{ .Release.Name }}-frontend - component: frontend + app: {{ .Release.Name }} {{- with .Values.frontend.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} @@ -16,6 +15,7 @@ spec: ingressClassName: {{ .Values.frontend.ingress.className }} {{- end }} rules: + # Frontend rule {{- range .Values.frontend.ingress.hosts }} - host: {{ .host | quote }} http: @@ -30,38 +30,7 @@ spec: number: {{ $.Values.frontend.service.port }} {{- end }} {{- end }} - {{- if .Values.frontend.ingress.tls }} - tls: - {{- range .Values.frontend.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} -{{- end }} - ---- - -{{- if .Values.backend.ingress.enabled }} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ .Release.Name }}-backend - namespace: {{ .Values.global.namespace }} - labels: - app: {{ .Release.Name }}-backend - component: backend - {{- with .Values.backend.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.backend.ingress.className }} - ingressClassName: {{ .Values.backend.ingress.className }} - {{- end }} - rules: + # Backend API rule {{- range .Values.backend.ingress.hosts }} - host: {{ .host | quote }} http: @@ -76,14 +45,4 @@ spec: number: {{ $.Values.backend.service.port }} {{- end }} {{- end }} - {{- if .Values.backend.ingress.tls }} - tls: - {{- range .Values.backend.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} {{- end }} From 3c98e1c1659f5730db0c4e14ce2c082c08192680 Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:31:02 +0200 Subject: [PATCH 09/11] Add authorized origin --- backend/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/main.py b/backend/main.py index f520815..c158d3d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -242,6 +242,7 @@ allowed_origins = [ "http://localhost:3000", "https://my-recipes.dvirlabs.com", "http://my-recipes.dvirlabs.com", + "https://my-recipes.aws-dvirlabs.com" ] app.add_middleware( From 154aadb6b76ba8866cb145b0b29601da3442c87c Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:27:33 +0200 Subject: [PATCH 10/11] Add authorized origin --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index a48fdc3..46d9f5d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,4 +19,4 @@ COPY . . EXPOSE 8000 -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file From daedc67e7afe9785342e5c674e1b7d886313815f Mon Sep 17 00:00:00 2001 From: dvirlabs <114520947+dvirlabs@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:57:34 +0200 Subject: [PATCH 11/11] Update db_utils --- backend/db_utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/db_utils.py b/backend/db_utils.py index f2c1881..37acf49 100644 --- a/backend/db_utils.py +++ b/backend/db_utils.py @@ -93,9 +93,9 @@ def update_recipe_db(recipe_id: int, recipe_data: Dict[str, Any]) -> Optional[Di recipe_data["name"], recipe_data["meal_type"], recipe_data["time_minutes"], - json.dumps(recipe_data.get("tags", [])), - json.dumps(recipe_data.get("ingredients", [])), - json.dumps(recipe_data.get("steps", [])), + recipe_data.get("tags", []), + recipe_data.get("ingredients", []), + recipe_data.get("steps", []), recipe_data.get("image"), recipe_data.get("made_by"), recipe_id, @@ -143,9 +143,9 @@ def create_recipe_db(recipe_data: Dict[str, Any]) -> Dict[str, Any]: recipe_data["name"], recipe_data["meal_type"], recipe_data["time_minutes"], - json.dumps(recipe_data.get("tags", [])), - json.dumps(recipe_data.get("ingredients", [])), - json.dumps(recipe_data.get("steps", [])), + recipe_data.get("tags", []), + recipe_data.get("ingredients", []), + recipe_data.get("steps", []), recipe_data.get("image"), recipe_data.get("made_by"), recipe_data.get("user_id"),