From a43260fca8ad0fcf3c573aa7a3df8e0ced221e83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Di=C3=B3genes=20Betat=20Roos?=
- We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
-
+ We appreciate your understanding as we polish our documentation – it may
+ contain some rough edges. Share your feedback or report issues to help us
+ improve! 🛠️📝
+ |ZZWxIEH@%c8M3D2
zNO9~Eni@=$l%>Yjv=Cy{@A=Lj&-=XJ=RD`!bI*CtIW;d@G`6<3Xl&R-=aiBslZYix
zRx&RmkxcPP2~cK=Pbvl>&`
- Here's a list of all users! + Navigate through this section to efficiently oversee all application users. From here, you can seamlessly manage user accounts.
diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 04e2f80f4..9b1d35b55 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -88,9 +88,9 @@ const Router = () => {R_PZNguCqRn?F@+PC?K}YN5Q$9VvFbkkrJg*(1NXu zJ0f^U6L<8k3+jho2GPgo=Kp^gYBZQxM|RTD9uIe60^JQ1=-xZR{UZ#D9I6@cj|Yx= zDQXpLBlH4QuuX>x;m?_yHsbP-Oxv870NNXx)!bsc-fO-zj(nZtMQ9(@IbIq|`WJ&7 B(5V0b diff --git a/docs/static/videos/langflow_build.mp4 b/docs/static/videos/langflow_build.mp4 index 9d068fa01ee23a764d24f9b32c44b871ec4f445a..fffd21aec7ad2ff37eb79b622c8621b2f9fbc0d5 100644 GIT binary patch delta 566 zcmZ9^T}YEr9LI543qi%RSTWuerm z>>>zx(gkhF_6R~suKi$^tPoAIVI>$bA}p($pqnnnB0KIT=;HG~=X*Hkj6IxTene-O zspE;kK!OAk3<3!@93**h3-TiOegG@?xmu3oBbyY|R^%hf{++nItb8$s-91I+Ed0Gi zrx@U3(eyC7N>n)(L9NQfNLO{L8&K(0>T$xSRNzzU+g1`?RU^7u+;AH2_G&)dlOAO? z%_NuAP$g6;a@lQ-)9~KWw%IW>sr`9`^szehvHIpZvf$EV!DYD8h^C<7dN&r|8lpOM zeKCB04u70ys&Morf0n`gBCiY~bHI4bBKd _X>Ve0(gNtR%h_0lpQ{S&z;?;#`<`MopBNvcNNHZ7;^H zn03vD?m6qXUua9&E{!15YJa+nq3R09Co;)ZQdN|Ls>UQ&;~gaHqz3Dx_TU5>cGV4d zNur~Ua#AiNI_hIzlcn3uEl7vWj7?6DJGJP2<-XZOPh+!mSPn^%*KQ3m!jAs)|5~Cw zEBTh(Q-~Nm8;qQOU}odTTx|R>wWqLha(cvE7_6M0FthT;1(D`3-EC&!=6)BA|DGo4 L7c4$=9^cYmI1cZD delta 678 zcmY+8Ur1AN6vw%@yT9(%cDL!hb92+FbGobuE1Cq(h_Hpwh#tn44F(H}5UeB+WmZe* zAsAt5giF=}17&XhVn$GmC=$Acpu`9fvAqbrgzcd}-|Ot5hwu5EbH3-C-)}3iKutYe zpk{*^iX9GSVju}-=!r1J#!A8-QWBWeD8-Vc$4n!G-u5H|$sjq|o>UARfY&D~-cEr> z`jk~Pc=m=eM1eCmm2(fl5UVOleZN*^LVxtSPIV9EFP%#L948ey6;b5W_btfdD{2y7 zsW@~6e!f%l FVD~%Yk`J_*D%Vef^uU!4yXxol4ztkxrsBqi!QU5 zyjb1;hf0ZFj!uckrEshqnBD9Y%E1d3810-qC7zJngHqVp@RTzQOWCJT^}perQwT(C p {Qo8qU7Wm?Y>l{X?>foX<%v!2D7)A)D}Alw=U;Ld@*n^J diff --git a/docs/static/videos/langflow_collection.mp4 b/docs/static/videos/langflow_collection.mp4 index 69d1727769965035fd2889ff2018b9c79f785547..f240d1be041bf6280bf342dab7e0588c5df96cee 100644 GIT binary patch delta 742 zcmWm7Z7dXU90%~Z=sb1jJab1Ujy#3P AZ zKo8}7j{bApnC0L$d=y8VcPK@wj})oF8!bfh2A+)+qdtllH8=VvT0;%* Sq&ImQ~i_5#E&@j}c%8@$(6NchXUX^9Vf{y-A0 z=kimc{;xc;K*1z`wS)bbu^)kW0fmm{- }gHHYB7Y>PDW)U*>Zwp>x6oVs%D%~HREEdL94mM+z@^Cl2y^Y+91h& z2!kar*RUdV-Kn6ZHdn@tIf }$U&O8P1h>Yk-Icm}y%=xi6ywAA zGJecy#vfh10gGxX+73X`_L {9=# hM^h zc-DmUMa5nnQZ lgOwcSV($Mil?&VFTT`JlFX!_p+uAU znbs5+u%<}O5|KP7l}TeRGU<@#T>3f)kzqz!JykSkprSc*U>L%%td2q&?a#t!|7F=A Pw&SwLjWLj#y^-(_%~MN< delta 904 zcmWlWYe M$J$`KWLef z8KU5@EH5Wi^n J?n_-r#4TrFU8#^BmHHr;^(nInEi7Eh~;}?Y=#|5*XW@HJfqm-cH zrtBem5MzG#BFybJe|{T&an-uVUaJVPAVr8}=R-_yPFT|Bgw=;})aU}!6u mPVC?nTQ{JRts{>Dws%%KAvh~)ZqxQ7>(qhVV zcEFybI)`a3&W12Y;6#Qn=SeHf4auF|-yvV*y3it5_$mf Yh*c#jGrMUQd(6)Bqi3cq&qH{QUC zuFW3=fa9cV6L;d1DA6VGXB8Z-4IKP|nT`4&+Nck1_eWjX8w^6g32+jG0tpBM;Z)fx zT~%Q&)*z)=!>Mnd@f=@$#5yinYxIgFqgPahEow#*MT{g`R)D&pDH_C3LsM*x5c7t< z*ty^EXiuDjH~LG{aX>~(({foGi#Nzo#+z<|Qx;F0vINmKM#ZU#+t$c<>O|tHlQiFv zRWZy<{4w%aWe)DaLUnS`B{+F1`MRk;F{H3~3eTpcRFuMEU5aZx%=o3U@fxjSG!>+g zVl@3t0P?>Z>BAAohio%w$Tri`bo*nCncy^yH7cDlab8|gf-G 1+*3o h3?o~KDeky}thB$FO8YO!O>@sOOU5nXy!?`#tpA4oQ5OIJ diff --git a/docs/static/videos/langflow_collection_example.mp4 b/docs/static/videos/langflow_collection_example.mp4 index e58ea31e4ac55b1c7568fb0b4b7ab87fdca09fe2..31cc949613d08df410ffda5c9195e9ea64faba46 100644 GIT binary patch delta 1654 zcmZY0eLNI)0LSr{>hg5xA_;LN#}O$xp}IWg$gxNsIwy4M 2$_HD-H{OA zSbg}$8a$c{TakgjHMnwv{7$rS3^l0;Q5v$pMcQs=0Smv%na{4{;W{Ia5G*V 39e0y$p0H0d*~Eh6eG~2}d| +_eF50n02JM;&ycu+%{zFUE&@9Z5p7|`KNK1J7a&IJ?fWH@Bo;{FPUN(EBd9IiXS zZpfiaiIF%*>umC5*bz^Lxw##X6gZ9Pwr?zUvLyUQC(0T8#+7GW;q%QZmIcD5S1Hy) zZsFqm2AV~#&zPicxhv|HuWG*VUqNU=xlyaBHT2;w?;>B-uf be;x1lFv((?b>sys${%-I;*ic5m}hd|Tj?YD4_$ zeBf8NK8TC*-t}pI *oiqpTF1` z GqQjxEs+$3QzAd!LK4e_~S_F|CUGZ9B%M;}ZCU=Mqot z0MA8a?SjZSk!TgPHyfzd2gS= zmM!;HV<+Sp=D2%Xezp|_nfX@&!9%_*0GiqS>|bCxQSjy|EbhPm=~mozEPUlphV~Xx z2Pp-H_8v-j4(Ftzz~c7I$)X67<#CuQrb>|IQEKHyl(JIl2$V8%(G7l0 HDsnx^lIRL7i9Q;yhn$3?K`(GM>R9g(4lS$nIf| eBx(_bZRTtS4Yfd#)18Yug&tBZCseLekj-2C#6_=b;9$X}H zRdYE=30F0Y6CLge=y28ll#o}QYP{ 9 zJ$58`_6&8F`WV5p=OWT^a(72V0C=4pi!TykVkgxFVPd!aYLZ>ujqK|4Q7ssB*UAs; z8~jxCR54vN-k{b#I%lc=v*Y4CNPD!enMm!`P8lF#SbN41FJEYfw_-qWA+Vbmzq|nB PmlyA^!KCviP4a&MothhS delta 2077 zcmaKsc~q2T6voe}<1)@jBWMaZfXE`^#?Fk2H0&ZSiLwn7V4xtV2_wTQpnd|fs521T zBn$@H0D?7FP0=z5lQ0Ag!xjoVc20CE1dOTZ`TD!lAJ6&S^W68o-}l|`zVG#W54p$h zKjdEKmvICI{4xRu`Z)sTyVx1fOlTH;7t1YK&k<0viu=nH7+E$uy$9@%oYR#6R^6E+ zj0G>*%)Q0>`+uMNARc>ogwG?7aNgH5;VqK!=9z;cB`>1|eA{;Z1-5o$>3qwtK-oTi zrVt!d@?Ug |E*JG!U4Ir zMMu8@vosd1cn5MYx0H*(Lb2s_XVgejEFmkBrY!H958otXd0{X7x&Ujc3s^CD7IE6p z3Q8NYvA0FOj$`|549p+5?JxpexOS-)VDk#Ql3dWW(XP`D9J_DVS_;-j*qf(fkDI;r zbhCG*LI=+|)8WfG96=MWz=8b)UY!H9ig TbN z<$^KCjf~xhBiW6t_WO4T7Oi=~M|I5dH59Xa?fH)Xt3;KfnG18qY8|v5a)O-cjFroj z5_=TaxKMG8t1CMQw`aIDb%6=@+}v$I%`a}@>%d8o``z8(jWqXdzk%};JSH8$8D~8n z=&?s%fG70@h+CrJ sM_GS5}x;+4N0%T({VRKn@Gp?x=;sy zht{iTCpa|jRTqbRdiZ8a5BJ{t9Pv}{*XXIYRK5Ut$#$v48dMyS7U28ok6dZH8F;Bi z8i*gxjMYkMtkx%ZDLgN|k8&ZrtKB}3FJ0~SOJO@~JL$*OfUDI0LgZ;flm9efokSi$ z5_w?P5qO;b!1ZpR)4ZTm_Mp7058~g#nvS$B)R7h(_&q$so~ &oHQ_28+MDGPph^tTg)5d-y25$z2T49mM(i4zL#xbo;u>EJz(N!M6?&y zbUQ{uQPk}yGhYpVBwQw23wjsI+|aVhqm8oTSY~LHk)biVgoo6rw&*qi=zcSL;u^U6 zsN54 ;2+dP;+a)vjVw3 z*F@@bO=`c5IDaCE@+S^lmLhf%CzF#nWgn)Lsg|cuwftc23B*gE9i*kt4jnp;xbW~{ z=q>076&_CY#^f-csZ!}PRa%c4afO2dI!YA|>3_e#GgTH!Z@dpZ(kl-x27NwPUeto4 zhcd4Ff=&926d!PrAtUuiuy8o@FZMb(`6%n^PH>Zb_7wYW9tX0a94MDO4jhXfN3J3! z&$j@_erO~Qw<0s-k`EO?$7#srgqbJ2iR2T|Nt#GzuCRyhqK2twZ}1@QDX5TwxJ5UY zz@H;3Qn -3!-aM-d$QVg2wN}e17$IVL(iTLZkGNt(I zlzOVvh%GB9FMSKc%8XB!LS-~QU7p8wRAF-lI!g+hbNT~V=XmzqAUpH3(p1?+VBWOK zl^s64y?Q2lF10Cu?g YcvN%$eXQd)yz|i|_+7#0sVlhtniz2|Q%|`}gI5^xo!>Tu8H0<*8m_T? zFk34qH5Twpy*lS5nwq#|oID}0_;%I@g33{q*6+U(jE`DNKI+EZo3M3te @5>JIeR<`v6H;cY-hW5=4+gL|^#A|> diff --git a/docs/static/videos/langflow_fork.mp4 b/docs/static/videos/langflow_fork.mp4 index 03c280c35dc720b1aff4199f35a568cb4c6020e2..c9b75bc2303fc50ee7456583aee46c74f54e70cb 100644 GIT binary patch delta 3743 zcmX}vX;e?!|G@F}X;4bj7bQihG|`~CSED2(DJf~7iBd_5t5IDIH(Q011`V1-niW@l z619^bnd%yHBQE8Nt~7`wlK=ib`9FBQAMCZyI%n;(_CD*BTu+uDt;rJgN >!KDgK4XE>6A=n1 y_!7)kQVh>CY(hbqV znGD@WM&x0KE~AGJJM@VmasRH5?%!8j*oC6r20j`Xbi`n8Hi=U(#5e_`aw%N8%qX}R zPu?{;&`pGje?g(*U*ozl@wb)Av3PcLWtA&&urX##7*lkxF@r$jSZBtVqhp=LR|&Lj zu{eH-^n|X$o>0s9jp){FSt^0wJ1oyvU}@OuS(~u(+Ui2<1{*8SLM$t^s*pg3YO5za zqSk7~tU +1&icFfvT1D7mVt0YOR)Yqbw`Z~1?vb$*=?rvIt?hcA4+I-9+KV&!H z4_Vu2DH5q}i;?OZyLe>1$wtPG*@Wv&?DKDt{-5^P|I^`i4XLWxj8!#`?l({ 0{-J?;U*qGQ;Ln->nKLb>6r8EWWYb=~sxh?{?n4g}e*9 z{w*b69R2W%qyM&F(Ef;j(gA$<)xX}IBs%TJM5h1&ACi12fC W{G% lB;!~FU;pR6fzw=1fmlW*!lDfYFR~M(1dXRso(wJk+apna6JH=g%!{W&FQej*Z zmp*wHO*fpp9f5-Mq;?qU>Sg#VVwPP-jv+2ToG~i}4Mj7@f>G^Yro=U3pr3^X`q?jR ziBwEBbBf7fa#1QKZ&MrYFwKvgi{1A5QVZ~ne|}^p?wHCK=V74u>Eb;o8gcrjDR$ML zE|0;8Wd#BT80}dQ_km2>7UHCBQJ*b&npuQTGm9k)@SR?Xa2gIbm#lUox)NtlSEBTQ zD4v;LDr|@4KBZUoqHkwuoEv$y^enzwdaiy3)78peh>;MzG7Ql>@1Tg+?9RtVph^4r z03>Ik%b5y16J0qiON!Dfu_(Ptr4p~3RC^2H67%Zv{p3z IjK)a^Ni2{HA%k0cLXY#6*$?Lo4(U&5^Ie=c<;&6obf=1Z4z3eahD!wD_Y zAJxD#Vt*7SDWZQi8Y*L61AaS>S=LSaZV*9{D<~+^>{Ew+jm_&SvC8|ZfgS!+byZ+3 z=^MGqT*JPR7IPC^q}lrVOQRCitU;}N)Y2A%=Cx%AlWF-joR+^H`2i2kYJa4JHtOwj ztZ-{UyVMr+YidtR!!hd{9`(fT^9{87{J-Ps_-x@#H7PQqbrWZ_Zh4EMm%**88*yRW zEoDnIyLRj2ZPZEbP$@%4?b~N^$oTc!IDY+(Y7~(*z02G~S=0O2j9s7aFA^soe%@y~ znJ)bB^RJ`nBs~2AhNnODn2!(29@gt%!sCZBpD^>*BO7J%{Ou!r{`PT~KUQt(Hs6o$ zTDr$8NpHXt=5Oo`=uy(ZZzp WcvO#acv+&rDH?=ZozH(r{09MNkSPA2>?0~j-4jV7U zw1NTljjmH0IL6~osqy>&;VvWecsL-Ki(Wx*pVbm$xp!zRHyCvcFP pUGWRHD^5ns5p$zSG&lO{B#NB**L`lX7eKk)P6)9q$a87z#N##r8|C?02)9Q-%3D! z7R4AzIP`!T+DU= $O3iok%R3f^kSYIsMc~%)-M41WW7A}gmJYf z5408+f=xS}h*kjCt<=6t0X*)}iabSdcc21;icm41R{v0B*Kom)7Xb}^yckaDQvo3* zIChHT@|A#c`N|L{%t@3e1C=OQ0!BV9Yg9mC9`%~10wM}@x1I|819a^Q75E~>nOUg- zHM3HMJA+hMTMbmLIjs;iU|GtKIzJjfwL&!E21kddHQ}lmtu)pGqjc`&Z7rZLZ)?M1 z2kwXLGN3 `$N }r{l37mlb>BcOZxqwY7Vs<@q;>7`hA^0>=UDV9Q{Gcg~pWmgY `FSqOkvV${U7y>N&fSNB1g=R-O zxhE99n$oedP|(@UEuIqwbn%@1u;CpS@M%BW&7Tf{OEYb${S9=|xFO?%?ByGW!)$*# zQ5X)!BAjknI9rXf2oPM&84N@KH5iD5@)X*;FA7e`(xBuh&~)N5dZK`4^hAUHb?!!P z4A2|7hd}Kp9aN45H6ABn7|V8=VH~tr(0gHV;Pi!C9Pm4Pl7PeTEr81Zbr{+m=!-@1 z&@r1k$9}x%IgJD;Z=##}5}=@v{uh@BPJ`5ZcM`a4<}w Sx?=qin17$z@Cf9r;9gl1U^xo(m30cZ%Tp826xgXw3zAac z32+CJQ}{{cCyk$DKo2Az2fyQ75AOuf9v%lluQ;o5j_tMabm&Urq$5rOO9oKsh)n2x z&kgxxvE}p0hLw9c)y`~o5_g{BCx@S0e)9Oq=jSv#qB{#<`7fNKKp{{^fg !{m!p=fC19sm32dzu+QUCw| delta 4558 zcmYjTdt8lK)c!50h|`slqI4req-N5ER5+8O Yik-9XA3__MUe%m zC}{|Py^9iC@1h*8i`*7}<*@I-Gj}PsVm_Ji8)aY+Fqj#?QCa>6*IUmfkp2aRKr& z4J7Z4z%plv<`~4XR|u#AYAkz&`h$Ilt^25+7Y$aA&?wlCobo;mK$DgCY3=R8*rjtA zFr2w`YX547agVtU>oFg(>MQ&JQ(a&r3ozBIEXN!=kMts>;O$2GO~b%mHu@G7$V+^u z&l2B_I_w5b-*$9a0ko#aqX7fJkTpFXbE*tlY=#kw%`h&xi9E9)lfU+}uU#h31|stJ zXA}1KXVV%nLyn8t6zs38vDl0?7LOf#3EI8zaV=KR&gG5+%-Olz@g^EVKRe#xI#?t2 zy9YB7e^v7xdsQ>xi6{K)suS6DRf{}7#KTrutT7k)=@xga5i4x404A}*hRHH9uf^BN z%;Kx%nHB8OvME7hk?F8=iq=6yJoK!Xhn{tz8nmAA))9H&pc~ff?;`eR?)U7^+#eF) zGtUh+hVkI2PMg!-h>es^1#AI3Hd1O&VbJu>+5-+u@9eZ*339b+rlp;Sest}0c66;{ z;v8s(RgU>0R@dNo;v_gMYKD?KIP&6*BQ7K (vsofK*6MNaoLF~!RoC%~D# ztZ}KG0{z5$7d!D9PjqG)s3P8PI+OLA&N4}dZ(KQx8CT9eE;g=VoAkp0XqDPOvPx~& zSiGjp(#(}vn$5W-=Ix(42XF)2S^w0zIai@&_s?b7{qwFB!w)L f z4c^S8!Dj?+7jt;*lRgrBtJfz4b1^G(UoRY}EIG*+@MFnIOR9Eb?5OI`995TI!QRLQ z7B6K3i eyy+b92}Iv`_`PU2#7FajYvY-fuO=?(Ol+y*(iVH&WJGzJ;}xZ@p55{NquH zqkjU|y-xfj%7m3|`$rr@?(J l3zWzLAizr8FPYRRv>|Q5sMrH1vd{GL1U-kg0KpL=@`F)XBCm=q2^S*qs z7c73?=NsTN(*4&&Cyc(de|!}9s6~2+K3LH$Jrg~E9o~|zlmgCI&UhaIPT!Crxrq7m zCLLgTlMc4H!jFqR2>c9W0$D6BHrxF&avN-OHfw>W&&^RA0iGL@vpEC&TW`(~@s77u z4&{Y|BR3toVh6S=Jya<6w|V^G0aL&&{)ZFZVt!xOBh1$|_lfu|+l*Xhn~|q_7;)*O zql$aL?o~%;cp;WL^cR*oG=KddXy1nAD~i6U5SV`fXUZ-%<|p`w=S`0>ThrsE-=MuR zE@% bfTk!L<;Xi|K#g>NdBuTRM zxpM3CMdGwt{m!#izh6yp`D6pn|H=lQFUu5fcxYbvUUO*XG37u7Gml|OVwJ@Qm=!9H zwU)7e)4&g%D xeY$#p_!<~?R09{8VMmPv zzO~GM?4@Tt;OV3JxMFbV4ep4YW(8W8(@`lh8{NyyMz?k|PJ*3Lth*zsHqo?hs1rCf zv`%e4_z ;=55M8a>plKQ=|u3!JAY_&gD+gUBgNkBY`as@_UEk- zD7W11I^g`6O3mFa?3k=C^d9gR>kGYaAU+p*d+%RKg8oF~0ehnHFv|v7jO)YYb>QY9 z&0}l8biMg9R>NxVHvfy7V%4q9x>Lb-bsnt|yWlkak=hfm=jfKrqK7rCZUO#g4XYn# zy@np(-5Q7*X8csEk*Kq$yp=uWPZYD^xA{C}Z9dPO)nsGCo&nF<*svGT_&~8EeJ@Hh zNRkOQVyjhO?o)=?H~i&zEJcX?vw?}lY1bX*qm_!M<8pQ6rM@%Fmk zIv #~r+-@c<8_{g^J_|z<3*+2EuGCA}r zy&hJj_c>MvzLnKyW@XjuCF(8NzBlp)xLfhdeRafcXnz6zWjD0Hn&AGij;OD9eg|K6 z=zC* LYr6zZ1mQBx!h z>^(m`QjrQB5O?ubBp4-Ol!?1|528;8p-+7`h+qZ_qe6V@yCJ0Z84pTd45e>40zNB2 zLVP8hfiESLgTv;zv8oi;1V?v{8Vy+i$D~A!Jk#K8v{R=^*cg7SR-F`4U;Lz+2DM?? zd~do2wTI#tqrYenkN%=bx5Q{>pccUlBR(^5IBBCD=J_qtrUwX33D71!B|wLkpgr)k zEFCgNEyzn}jUZk+OPB6PVI-^2B`&KNNmASbek(?gycfbTy{Sij*h#)MOP_qjLLRi~ z(@ESzp4g{P{2m^4yfccp &PGH8WMk;JBF+S zk)+u_hV)Rze4>^SDdR5*x3(~%zwx2v8k3Ev2mM8^Ji~~%@(g3T-VV8GoCz7@4}`os z!h~Q l)YSQs};&- zt2N!jp~~Catcka`eNQ8>jJ?r6(4}Y_YC$u~3v8Z77F?yphHm4Q Ds^wEioaXGlxJZI9xCdq?SoC(H7n3=-N zA|9MFo9;)Gq{d!KHEWRFyGBZTb|X8fS4upo_eb)%kI|#st^_lO_@moy^idr6?NE1u znJdgZ; CiN6~kAe8D2p9Si4R<086%&dlfSUQ7k!k= uuyk-T%7AMws V&@5J@>kc29x`X1Opch}%60rqe@_{^D&g zo%M#3F*AfB#jB7<2&pZCqkcSu)YI^+p=Bs>L(7%quL(KcWhG5dfn3$Nl6X~P7^QY% zH2dNzf(aKUg81x Mm6%Shd0qwac=C`CgSW^G%3u$ zsOot%7WI5H6=I1zXiW_5#b)r%q!=0}_E+bx7~(pA#nR+jjHH=A5tnAhk;yJ3MHs}B z30_eyw~Qw)w@jd#X^`F5CXiPT f@Q^;2rvC~UZXt^1jq1#jF z9=eqLX;KQo>=tH^Fsa0!CZ$oZIM!Jjdx_7|kkhJGjLy856F>8QAKgmAD1PI9f=MSH zzcGWFUqf0PaDe#YfP-YS8l$w|4-(AJ!ek1QB}}$3Il>$wp7#4;n(#eI(k>jKnhUwq bkdCD#X5^B?YI${kF2UpxukJre?g9S;?eq~5 diff --git a/docs/static/videos/langflow_parameters.mp4 b/docs/static/videos/langflow_parameters.mp4 index 370ca5f36819a62a1e4ce537047e9a3ee3fb51f8..c7599e6493879a53058fdd8cda85f8e1cfc807b7 100644 GIT binary patch delta 1459 zcmYMtX;2h&7zXfxh2=g(WDx|p;zl_Zl}ql+6+xCmV}TX9L=ae50m0?wFUzrjuAGWV z3hE>zxX)xZVkE|tWf)jCh6kBKDl7(>rTv>e^x^m6ndhDNxp1OUxw5BGd594(tQ26- zH*EZir5Ak76fW)|FDe!AqEfNhpBx@jq!>6nrsR@@L33q~naj_ZVLOOxB$HC1R4Fw| zozj47q^7zS@!@I0ho@B_!^e8sb5-b(Y7e`RsGHi94yB8zo2xut6Y;1XwVKkW)<8UJ zASJ{sj0LkW!$1v^$~8nPckNCsBIB=xjBj*K0{((gjtah6G5V+hW!1(Wn<2YmJmQM| z&y6pr5!Q+^wGONmla@v@P;E-Br_3mG%7U`QK(*DB21F98&JtiSL_1 zqU-AiU0?rop~R`qp9-J?sURvCPIV#S+ey)62o;K=$*@p6TzeBXJW2|#hEv;6aFrur z5XLA6jM421%S2fgLG6IDEOOKL@D@iUtwMKq)FUwwS?`3%I@-+?mx7|DRtVb{{i75X z@1lozxRb!GH%4L+cg2gu4s#JZ9MfG$7Hwj&XcHG~iOrliZy^dt<8tiDqq#Udnv2(U z!V+s2>mD@ZyUyzq8FM%FK4i=US4}+AOPEO}6M_Ux2zckOqmZ3An}*$8i6>j|hkeqe zeq5ML5*5RqlYHL`swCOvS9l)adyW$!dk=){lxcTt&q`U7fRr04D(1*~l44 e)kV`1xkHUc=o#z~j8!hSkWe$(>k%M_FFIJ;u)FwUm<>>wLsm3;bA+`Upe@(2o&>4uZ!h zaEc`2=mLnN3y(5j8dtdO9W=fxW*Q M&hSRsWQY* z9UebMOtVBV%_{#&PUd(OnB!G;dDAR%RfNt$ji)+UJX1+kQPo&HQ!^j>f9uC3QGvCH z3aq uBDk7meawMq}84>lYJd}S{pPPu6Mq(2SqU9romQO?Q zb;5nyKs8cL;J$6P&iwCxJ6$)x=b4iHa>9Npp<2Lx+FDSI0e)L+3OvW!q<`b~=uwMA zESVf@)h4x$$587iP2ym-NSaze!sn$3pFggpf%xU)^C1ZIX)kxjn4o=T75Zx08xO(x zUHhNPa8T|TH-Ox@WBOZ6%y-Nu<1(jnF@W?pcT!#GZ|=7J9k;7{c35HkK#zMCV)ah^ z8~}FgiNJ>_-gGjc1fLF`6mYSD(aTamh*IxUC2Umdb>bs&U++jT(HrcA-r%XPy)iR? WD%}ks>pzhHMm#i6!$Y%Ap!g5*Nv}iz delta 1793 zcmXApdo M!T_m}aYof_zatoylQiOzDi$Q3l#$a+C6GDj9IZ2(cQO~J$ zPIkL)XZK`FwcEC}IxX#}Q 5toSuR-xOo16#4a~tBl1nTe z;eC`UvuH~~-Pailu$I2gSgPtG&QY`K(SgqyS}h5MzfH07TLaG?xB7Sko(!|D@x{E{ zI%~?Uvymtx_EBzQ$1Jhw2OD5ZrXTD!aPYiy!4BBd&IJei2s}$d9Z3?pE`?dQ*DR+| z34E@?N$o9igF *cLL)W4L6F>aR2x##$m7A!Fu2UHULiwd*$^| zjGSreM!+M})J<2eq3 dWbFHLy+83$p!Sm6iw zU^DOs0kl{VxFrm^zz2aKhyou32iahL-RIz;QN(>mwtx`w9SM~w;ragcP`ZCTjP(iQ zvCCV*HX6GezJ45YIMv(Zwc+IU?JujDo|Zc(&@#ex6=KEy5zQ8`W_rYL&G6Khh`~6x zC00;n1e+BK=CO^XdP4%z8;Wet$M}*}6kW244q(2^d`L8}0CpXX&bCFaTM (rjp z4RF70{H0zv@j-kkvwqJ*68>P0)pwN%&QtK4>_o5I=wa!$mn_|q9=jv
PbSMEFT2+mRL8)kZ0SSC-HpofZoGO75dG-3}>Ye-7(EbOx> zl_!NoH&eIz!FN^m@Aw@ynAq>t4hu@s2BKm1!!%`lSK^eVpX`M@Y%)GX!m`qgcl+Ve z$&7xim(*T8lkC;A2Hg;Q=A9+K4rhPK(h^Eba ?JO fZ*a`Odv!JjPwf*sjKq2+V7yX_4e;r4kI$sybeUNpuE(V9m(OTq+b547m zMJmj9PmL7Gm~%RpD&j0-aa%fXRXUN~A(68;f^oy5V`iBAY+M9N=-K#@+f9hSPm#H> zrQ?~UG@g01?L8{8 E1#)XA5SI z@hfPIU->h0`QhbH>=DbKt)%=}X#?{&l#`Di!%|TB(DC>-_+?m?UJkr4Qgv_-oMu>U zVvac#ylSf8)dXPkN$)MzfLc&T_ZI6d(@=+BbybX9-;@kAP~Vh1kJ*UEi6`W^!06%+ zCz=;w{%E5)(>K|qNv?_dIlCsBvuloLE-^u2b8;SHq-Z8moa8bW;LW*{(}C~+ujMGS zX&r?vFPKBhEpMqk47Yu0`9}qARBpY^eBxb3t&i`+>!w?$6JW#8wi!NpYSg!ZQ>0Pf zZas&%tE6KabD+h&9qt+M746RFsH3LH&dn3>6Zg~nV%T8dv`_%ou)FkFaF0^gYvwYo vR_k&|gek3SH~{@K`@1N!zxyZV3wca;r@F$OtG=0igPcsGhh!ROgzWzS+QYv# diff --git a/src/frontend/src/components/AccordionComponent/index.tsx b/src/frontend/src/components/AccordionComponent/index.tsx index e19bd3d29..06ecc36b0 100644 --- a/src/frontend/src/components/AccordionComponent/index.tsx +++ b/src/frontend/src/components/AccordionComponent/index.tsx @@ -33,10 +33,10 @@ export default function AccordionComponent({ } const handleKeyDown = (event) => { - if (event.key === "Backspace") { + /* if (event.key === "Backspace") { event.preventDefault(); event.stopPropagation(); - } + } */ }; return ( diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 2f3e79a68..25fe48a8f 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -159,13 +159,13 @@ export default function CodeTabsComponent({ )} - {tabs.map((tab, index) => ( + {tabs.map((tab, idx) => ( - {index < 4 ? ( + {idx < 4 ? ( <> {tab.description && ( > - ) : index === 4 ? ( + ) : idx === 4 ? ( <>- {data?.map((node: any, index) => ( -+ {data?.map((node: any, i) => ( +{tweaks?.tweaksList!.current.includes( node["data"]["id"] ) && ( @@ -236,10 +236,10 @@ export default function CodeTabsComponent({ node.data.node.template[templateField] .type === "int") ) - .map((templateField, index) => { + .map((templateField, indx) => { return (@@ -277,13 +277,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -330,13 +324,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -379,13 +367,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -416,13 +398,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = e; return newInputList; @@ -511,13 +487,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -551,13 +521,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -607,14 +571,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -667,13 +624,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; @@ -726,13 +677,7 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList!.find( - (obj) => - obj.data.node - .template[ - templateField - ] - )!.data.node.template[ + newInputList![i].data.node.template[ templateField ].value = target; return newInputList; From 9029beec648073c75f7a590f9c0c1c4122ce19dc Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 23 Aug 2023 17:52:32 -0300 Subject: [PATCH 065/134] Fix: git bug --- .gitattributes | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index b69d73e97..83c12c72a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -17,4 +17,7 @@ # Denote all files that are truly binary and should not be modified. *.png binary -*.jpg binary \ No newline at end of file +*.jpg binary +*.mp4 binary +*.ico binary +*.svg binary \ No newline at end of file From 56a75af6dad1fb964ba9478ff14eba1ec79f56f2 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 23 Aug 2023 18:02:50 -0300 Subject: [PATCH 066/134] Remove console.log --- src/frontend/src/utils/reactflowUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 13c3973f1..1d74d14fe 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -247,7 +247,6 @@ export function handleKeyDown( inputValue: string | string[] | null, block: string ) { - console.log(e, inputValue, block); //condition to fix bug control+backspace on Windows/Linux if ( (typeof inputValue === "string" && From 13a60c872f01e34664c13a6e1a9b2155c12d78df Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 23 Aug 2023 19:26:25 -0300 Subject: [PATCH 067/134] Fix: Node deleted when press backspace on tweaks page --- .../src/components/codeTabsComponent/index.tsx | 12 ++++++++++++ src/frontend/src/types/utils/reactflowUtils.ts | 7 ++++++- src/frontend/src/utils/reactflowUtils.ts | 14 +++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 25fe48a8f..cdb2ec0ed 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -32,6 +32,8 @@ import { darkContext } from "../../contexts/darkContext"; import { codeTabsPropsType } from "../../types/components"; import { classNames } from "../../utils/utils"; import IconComponent from "../genericIconComponent"; +import { unselectAllNodes } from "../../utils/reactflowUtils"; +import { typesContext } from "../../contexts/typesContext"; export default function CodeTabsComponent({ flow, @@ -45,12 +47,22 @@ export default function CodeTabsComponent({ const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); const [openAccordion, setOpenAccordion] = useState ([]); const { dark } = useContext(darkContext); + const { reactFlowInstance } = useContext(typesContext); useEffect(() => { if (flow && flow["data"]!["nodes"]) { setData(flow["data"]!["nodes"]); } }, [flow]); + + useEffect(() => { + unselectAllNodes({ + data, + updateNodes: (nodes) => { + reactFlowInstance?.setNodes(nodes); + } + }); + }, []) const copyToClipboard = () => { if (!navigator.clipboard || !navigator.clipboard.writeText) { diff --git a/src/frontend/src/types/utils/reactflowUtils.ts b/src/frontend/src/types/utils/reactflowUtils.ts index ecbbda4e4..6ec40512d 100644 --- a/src/frontend/src/types/utils/reactflowUtils.ts +++ b/src/frontend/src/types/utils/reactflowUtils.ts @@ -1,4 +1,4 @@ -import { Edge } from "reactflow"; +import { Edge, Node } from "reactflow"; import { NodeType } from "../flow"; export type cleanEdgesType = { @@ -8,3 +8,8 @@ export type cleanEdgesType = { }; updateEdge: (edge: Edge[]) => void; }; + +export type unselectAllNodesType = { + updateNodes: (nodes: Node[]) => void, + data: Node[] | null +}; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 1d74d14fe..999f45ecb 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -2,14 +2,18 @@ import _ from "lodash"; import { Connection, Edge, + Node, ReactFlowInstance, ReactFlowJsonObject, + useNodesState, } from "reactflow"; import { specialCharsRegex } from "../constants/constants"; import { APITemplateType } from "../types/api"; import { FlowType, NodeType } from "../types/flow"; -import { cleanEdgesType } from "../types/utils/reactflowUtils"; +import { cleanEdgesType, unselectAllNodesType } from "../types/utils/reactflowUtils"; import { toNormalCase } from "./utils"; +import { useContext } from "react"; +import { typesContext } from "../contexts/typesContext"; export function cleanEdges({ flow: { edges, nodes }, @@ -55,6 +59,14 @@ export function cleanEdges({ updateEdge(newEdges); } +export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) { + let newNodes = _.cloneDeep(data); + newNodes!.forEach((node: Node) => { + node.selected = false; + }); + updateNodes(newNodes!); +} + export function isValidConnection( { source, target, sourceHandle, targetHandle }: Connection, reactFlowInstance: ReactFlowInstance From 1d166b6e6b5ca999c3184014e0676b4f0c774e6a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 23 Aug 2023 20:10:54 -0300 Subject: [PATCH 068/134] poetry lock --- poetry.lock | 91 +++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/poetry.lock b/poetry.lock index 186eb4128..0a734e2da 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1889,18 +1889,18 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] [[package]] name = "gotrue" -version = "1.0.2" +version = "1.0.4" description = "Python Client Library for GoTrue" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "gotrue-1.0.2-py3-none-any.whl", hash = "sha256:5377e7fd316b77df7be9e0c3c017d338bed2ba2e95a99fb44374b523d167ec65"}, - {file = "gotrue-1.0.2.tar.gz", hash = "sha256:9ad9b2536ca68676cf37dc663b64f259956826075e80a9cb3f5a3ba150355811"}, + {file = "gotrue-1.0.4-py3-none-any.whl", hash = "sha256:f016f5e317a21e55dfcee00fb360f2c7a33c5b87a38601e7b9e65cb898bcf7bb"}, + {file = "gotrue-1.0.4.tar.gz", hash = "sha256:2686c93b798fb2d3b120f067d21e66bb803b013ee6ab6fb7783093102f74603a"}, ] [package.dependencies] httpx = ">=0.23,<0.25" -pydantic = ">=1.10.0,<2.0.0" +pydantic = ">=1.10,<3" [[package]] name = "greenlet" @@ -3308,13 +3308,13 @@ files = [ [[package]] name = "metaphor-python" -version = "0.1.15" +version = "0.1.16" description = "A Python package for the Metaphor API." optional = false python-versions = "*" files = [ - {file = "metaphor-python-0.1.15.tar.gz", hash = "sha256:66e324377af7df4799b845fe8de4f81393db48baa76db59308f7eebec2ba9421"}, - {file = "metaphor_python-0.1.15-py3-none-any.whl", hash = "sha256:e9efe6ccd175d67b08331955c0290ff1fe83bb4c43e80a88a154e254bf47aaf4"}, + {file = "metaphor-python-0.1.16.tar.gz", hash = "sha256:c26c3e8a37ef1b195073d556c929180c2b1acf7590a801da8d1b9afbdd82dd8f"}, + {file = "metaphor_python-0.1.16-py3-none-any.whl", hash = "sha256:f3989a679f888cc0374593ab5e5a82b0f121c90b14d6eb5b813e861990dbb13b"}, ] [package.dependencies] @@ -5792,53 +5792,62 @@ files = [ [[package]] name = "safetensors" -version = "0.3.2" +version = "0.3.3" description = "Fast and Safe Tensor serialization" optional = true python-versions = "*" files = [ - {file = "safetensors-0.3.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b6a66989075c2891d743153e8ba9ca84ee7232c8539704488f454199b8b8f84d"}, - {file = "safetensors-0.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:670d6bc3a3b377278ce2971fa7c36ebc0a35041c4ea23b9df750a39380800195"}, - {file = "safetensors-0.3.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:7f80af7e4ab3188daaff12d43d078da3017a90d732d38d7af4eb08b6ca2198a5"}, - {file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb44e140bf2aeda98d9dde669dbec15f7b77f96a9274469b91a6cf4bcc5ec3b"}, - {file = "safetensors-0.3.2-cp310-cp310-win32.whl", hash = "sha256:2961c1243fd0da46aa6a1c835305cc4595486f8ac64632a604d0eb5f2de76175"}, - {file = "safetensors-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c813920482c337d1424d306e1b05824a38e3ef94303748a0a287dea7a8c4f805"}, - {file = "safetensors-0.3.2-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:707df34bd9b9047e97332136ad98e57028faeccdb9cfe1c3b52aba5964cc24bf"}, - {file = "safetensors-0.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:becc5bb85b2947eae20ed23b407ebfd5277d9a560f90381fe2c42e6c043677ba"}, - {file = "safetensors-0.3.2-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:54ad6af663e15e2b99e2ea3280981b7514485df72ba6d014dc22dae7ba6a5e6c"}, - {file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0fac127ff8fb04834da5c6d85a8077e6a1c9180a11251d96f8068db922a17"}, - {file = "safetensors-0.3.2-cp311-cp311-win32.whl", hash = "sha256:155b82dbe2b0ebff18cde3f76b42b6d9470296e92561ef1a282004d449fa2b4c"}, - {file = "safetensors-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:a86428d196959619ce90197731be9391b5098b35100a7228ef4643957648f7f5"}, - {file = "safetensors-0.3.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:c1f8ab41ed735c5b581f451fd15d9602ff51aa88044bfa933c5fa4b1d0c644d1"}, - {file = "safetensors-0.3.2-cp37-cp37m-macosx_13_0_x86_64.whl", hash = "sha256:bc9cfb3c9ea2aec89685b4d656f9f2296f0f0d67ecf2bebf950870e3be89b3db"}, - {file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d7d70d48585fe8df00725aa788f2e64fd24a4c9ae07cd6be34f6859d0f89a9c"}, - {file = "safetensors-0.3.2-cp37-cp37m-win32.whl", hash = "sha256:6ff59bc90cdc857f68b1023be9085fda6202bbe7f2fd67d06af8f976d6adcc10"}, - {file = "safetensors-0.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8b05c93da15fa911763a89281906ca333ed800ab0ef1c7ce53317aa1a2322f19"}, - {file = "safetensors-0.3.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:8969cfd9e8d904e8d3c67c989e1bd9a95e3cc8980d4f95e4dcd43c299bb94253"}, - {file = "safetensors-0.3.2-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:f54148ac027556eb02187e9bc1556c4d916c99ca3cb34ca36a7d304d675035c1"}, - {file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa98f49e95f02eb750d32c4947e7d5aa43883149ebd0414920866446525b70f0"}, - {file = "safetensors-0.3.2-cp38-cp38-win32.whl", hash = "sha256:33409df5e28a83dc5cc5547a3ac17c0f1b13a1847b1eb3bc4b3be0df9915171e"}, - {file = "safetensors-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e04a7cbbb3856159ab99e3adb14521544f65fcb8548cce773a1435a0f8d78d27"}, - {file = "safetensors-0.3.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7c864cf5dcbfb608c5378f83319c60cc9c97263343b57c02756b7613cd5ab4dd"}, - {file = "safetensors-0.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:14e8c19d6dc51d4f70ee33c46aff04c8ba3f95812e74daf8036c24bc86e75cae"}, - {file = "safetensors-0.3.2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:fafd95e5ef41e8f312e2a32b7031f7b9b2a621b255f867b221f94bb2e9f51ae8"}, - {file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87ff0024ef2e5722a79af24688ce4a430f70601d0cf712a744105ed4b8f67ba5"}, - {file = "safetensors-0.3.2-cp39-cp39-win32.whl", hash = "sha256:827af9478b78977248ba93e2fd97ea307fb63f463f80cef4824460f8c2542a52"}, - {file = "safetensors-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9b09f27c456efa301f98681ea14b12f81f2637889f6336223ccab71e42c34541"}, - {file = "safetensors-0.3.2.tar.gz", hash = "sha256:2dbd34554ed3b99435a0e84df077108f5334c8336b5ed9cb8b6b98f7b10da2f6"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_10_11_x86_64.whl", hash = "sha256:92e4d0c8b2836120fddd134474c5bda8963f322333941f8b9f643e5b24f041eb"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:3dcadb6153c42addc9c625a622ebde9293fabe1973f9ef31ba10fb42c16e8536"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:08f26b61e1b0a14dc959aa9d568776bd038805f611caef1de04a80c468d4a7a4"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:17f41344d9a075f2f21b289a49a62e98baff54b5754240ba896063bce31626bf"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:f1045f798e1a16a6ced98d6a42ec72936d367a2eec81dc5fade6ed54638cd7d2"}, + {file = "safetensors-0.3.3-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:eaf0e4bc91da13f21ac846a39429eb3f3b7ed06295a32321fa3eb1a59b5c70f3"}, + {file = "safetensors-0.3.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a07121f427e646a50d18c1be0fa1a2cbf6398624c31149cd7e6b35486d72189e"}, + {file = "safetensors-0.3.3-cp310-cp310-win32.whl", hash = "sha256:a85e29cbfddfea86453cc0f4889b4bcc6b9c155be9a60e27be479a34e199e7ef"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cbc3312f134baf07334dd517341a4b470b2931f090bd9284888acb7dfaf4606f"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d15030af39d5d30c22bcbc6d180c65405b7ea4c05b7bab14a570eac7d7d43722"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:f84a74cbe9859b28e3d6d7715ac1dd3097bebf8d772694098f6d42435245860c"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:10d637423d98ab2e6a4ad96abf4534eb26fcaf8ca3115623e64c00759374e90d"}, + {file = "safetensors-0.3.3-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:3b46f5de8b44084aff2e480874c550c399c730c84b2e8ad1bddb062c94aa14e9"}, + {file = "safetensors-0.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e8fdf7407dba44587ed5e79d5de3533d242648e1f2041760b21474bd5ea5c8c"}, + {file = "safetensors-0.3.3-cp311-cp311-win32.whl", hash = "sha256:7d3b744cee8d7a46ffa68db1a2ff1a1a432488e3f7a5a97856fe69e22139d50c"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:2fff5b19a1b462c17322998b2f4b8bce43c16fe208968174d2f3a1446284ceed"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:41adb1d39e8aad04b16879e3e0cbcb849315999fad73bc992091a01e379cb058"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_12_0_x86_64.whl", hash = "sha256:0f2b404250b3b877b11d34afcc30d80e7035714a1116a3df56acaca6b6c00096"}, + {file = "safetensors-0.3.3-cp37-cp37m-macosx_13_0_x86_64.whl", hash = "sha256:b43956ef20e9f4f2e648818a9e7b3499edd6b753a0f5526d4f6a6826fbee8446"}, + {file = "safetensors-0.3.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c32ee08f61cea56a5d62bbf94af95df6040c8ab574afffaeb7b44ae5da1e9e3"}, + {file = "safetensors-0.3.3-cp37-cp37m-win32.whl", hash = "sha256:351600f367badd59f7bfe86d317bb768dd8c59c1561c6fac43cafbd9c1af7827"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:8530399666748634bc0b301a6a5523756931b0c2680d188e743d16304afe917a"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:9d741c1f1621e489ba10aa3d135b54202684f6e205df52e219d5eecd673a80c9"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:0c345fd85b4d2093a5109596ff4cd9dfc2e84992e881b4857fbc4a93a3b89ddb"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:69ccee8d05f55cdf76f7e6c87d2bdfb648c16778ef8acfd2ecc495e273e9233e"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_13_0_arm64.whl", hash = "sha256:c08a9a4b7a4ca389232fa8d097aebc20bbd4f61e477abc7065b5c18b8202dede"}, + {file = "safetensors-0.3.3-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:a002868d2e3f49bbe81bee2655a411c24fa1f8e68b703dec6629cb989d6ae42e"}, + {file = "safetensors-0.3.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab43aeeb9eadbb6b460df3568a662e6f1911ecc39387f8752afcb6a7d96c087"}, + {file = "safetensors-0.3.3-cp38-cp38-win32.whl", hash = "sha256:f2f59fce31dd3429daca7269a6b06f65e6547a0c248f5116976c3f1e9b73f251"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_10_11_x86_64.whl", hash = "sha256:59a596b3225c96d59af412385981f17dd95314e3fffdf359c7e3f5bb97730a19"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:82a16e92210a6221edd75ab17acdd468dd958ef5023d9c6c1289606cc30d1479"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:98a929e763a581f516373ef31983ed1257d2d0da912a8e05d5cd12e9e441c93a"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:12b83f1986cd16ea0454c636c37b11e819d60dd952c26978310a0835133480b7"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:f439175c827c2f1bbd54df42789c5204a10983a30bc4242bc7deaf854a24f3f0"}, + {file = "safetensors-0.3.3-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:0085be33b8cbcb13079b3a8e131656e05b0bc5e6970530d4c24150f7afd76d70"}, + {file = "safetensors-0.3.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad3cc8006e7a86ee7c88bd2813ec59cd7cc75b03e6fa4af89b9c7b235b438d68"}, + {file = "safetensors-0.3.3-cp39-cp39-win32.whl", hash = "sha256:ab29f54c6b8c301ca05fa014728996bd83aac6e21528f893aaf8945c71f42b6d"}, + {file = "safetensors-0.3.3.tar.gz", hash = "sha256:edb7072d788c4f929d0f5735d3a2fb51e5a27f833587828583b7f5747af1a2b8"}, ] [package.extras] all = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (==2.11.0)", "torch (>=1.10)"] dev = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "flax (>=0.6.3)", "h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "isort (>=5.5.4)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)", "tensorflow (==2.11.0)", "torch (>=1.10)"] -jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)"] +jax = ["flax (>=0.6.3)", "jax (>=0.3.25)", "jaxlib (>=0.3.25)", "numpy (>=1.21.6)"] numpy = ["numpy (>=1.21.6)"] -paddlepaddle = ["paddlepaddle (>=2.4.1)"] +paddlepaddle = ["numpy (>=1.21.6)", "paddlepaddle (>=2.4.1)"] pinned-tf = ["tensorflow (==2.11.0)"] quality = ["black (==22.3)", "click (==8.0.4)", "flake8 (>=3.8.3)", "isort (>=5.5.4)"] -tensorflow = ["tensorflow (>=2.11.0)"] +tensorflow = ["numpy (>=1.21.6)", "tensorflow (>=2.11.0)"] testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "numpy (>=1.21.6)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "setuptools-rust (>=1.5.2)"] -torch = ["torch (>=1.10)"] +torch = ["numpy (>=1.21.6)", "torch (>=1.10)"] [[package]] name = "scikit-learn" From 8fb73e689ab52eac43ba022755a77023afe63bb9 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 23 Aug 2023 20:23:18 -0300 Subject: [PATCH 069/134] Fix: Code is ready to run success alert showing up in tweaks page --- src/frontend/src/components/AccordionComponent/index.tsx | 8 -------- src/frontend/src/components/codeTabsComponent/index.tsx | 6 ++++++ src/frontend/src/contexts/alertContext.tsx | 5 +++++ src/frontend/src/modals/codeAreaModal/index.tsx | 4 ++-- .../FlowPage/components/extraSidebarComponent/index.tsx | 7 +++++-- src/frontend/src/types/typesContext/index.ts | 2 ++ src/frontend/src/utils/reactflowUtils.ts | 3 --- 7 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/components/AccordionComponent/index.tsx b/src/frontend/src/components/AccordionComponent/index.tsx index 06ecc36b0..e00932c76 100644 --- a/src/frontend/src/components/AccordionComponent/index.tsx +++ b/src/frontend/src/components/AccordionComponent/index.tsx @@ -32,13 +32,6 @@ export default function AccordionComponent({ value === "" ? setValue(keyValue!) : setValue(""); } - const handleKeyDown = (event) => { - /* if (event.key === "Backspace") { - event.preventDefault(); - event.stopPropagation(); - } */ - }; - return ( <> ([]); const { dark } = useContext(darkContext); const { reactFlowInstance } = useContext(typesContext); + const { isTweakPage, setIsTweakPage } = useContext(alertContext); useEffect(() => { if (flow && flow["data"]!["nodes"]) { @@ -62,6 +64,10 @@ export default function CodeTabsComponent({ reactFlowInstance?.setNodes(nodes); } }); + + return () => { + if (isTweakPage) setIsTweakPage(false); + }; }, []) const copyToClipboard = () => { diff --git a/src/frontend/src/contexts/alertContext.tsx b/src/frontend/src/contexts/alertContext.tsx index 1741b88b6..46a0d8a3b 100644 --- a/src/frontend/src/contexts/alertContext.tsx +++ b/src/frontend/src/contexts/alertContext.tsx @@ -26,6 +26,8 @@ const initialValue: alertContextType = { pushNotificationList: () => {}, clearNotificationList: () => {}, removeFromNotificationList: () => {}, + isTweakPage: false, + setIsTweakPage: () => {}, }; export const alertContext = createContext (initialValue); @@ -48,6 +50,7 @@ export function AlertProvider({ children }: { children: ReactNode }) { const [successOpen, setSuccessOpen] = useState(false); const [notificationCenter, setNotificationCenter] = useState(false); const [notificationList, setNotificationList] = useState ([]); + const [isTweakPage, setIsTweakPage] = useState (false); const pushNotificationList = (notification: AlertItemType) => { setNotificationList((old) => { let newNotificationList = _.cloneDeep(old); @@ -120,6 +123,8 @@ export function AlertProvider({ children }: { children: ReactNode }) { return ( (null); - const { setErrorData, setSuccessData } = useContext(alertContext); + const { setErrorData, setSuccessData, isTweakPage } = useContext(alertContext); const [error, setError] = useState<{ detail: { error: string | undefined; traceback: string | undefined }; } | null>(null); @@ -39,7 +39,7 @@ export default function CodeAreaModal({ if (dynamic && Object.keys(nodeClass!.template).length > 2) { return; } - processCode(); + if (!isTweakPage) processCode(); }, []); function processNonDynamicField() { diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index f8ac36230..f1a84bce9 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -21,7 +21,7 @@ export default function ExtraSidebar(): JSX.Element { const { data, templates } = useContext(typesContext); const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } = useContext(TabsContext); - const { setSuccessData, setErrorData } = useContext(alertContext); + const { setSuccessData, setErrorData, setIsTweakPage } = useContext(alertContext); const [dataFilter, setFilterData] = useState(data); const [search, setSearch] = useState(""); const isPending = tabsState[tabId]?.isPending; @@ -100,7 +100,10 @@ export default function ExtraSidebar(): JSX.Element { {flow && flow.data && (- +setIsTweakPage(true)} + >void; loading: boolean; setLoading: (newState: boolean) => void; + isTweakPage: boolean; + setIsTweakPage: (newState: boolean) => void; }; export type darkContextType = { diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 999f45ecb..6a12f3edd 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -5,15 +5,12 @@ import { Node, ReactFlowInstance, ReactFlowJsonObject, - useNodesState, } from "reactflow"; import { specialCharsRegex } from "../constants/constants"; import { APITemplateType } from "../types/api"; import { FlowType, NodeType } from "../types/flow"; import { cleanEdgesType, unselectAllNodesType } from "../types/utils/reactflowUtils"; import { toNormalCase } from "./utils"; -import { useContext } from "react"; -import { typesContext } from "../contexts/typesContext"; export function cleanEdges({ flow: { edges, nodes }, From 9686542f0a6105709cb299844e284ce73a3e5379 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 23 Aug 2023 20:38:40 -0300 Subject: [PATCH 070/134] Fix: code is ready to run does not appear after build --- .../src/components/chatComponent/buildTrigger/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 0967952b5..9eb25bb37 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -60,6 +60,11 @@ export default function BuildTrigger({ ], }); } + if (errors.length === 0 && allNodesValid) { + setSuccessData({ + title: "Code is ready to run", + }); + } } catch (error) { console.error("Error:", error); } finally { From cd6985591f7b352b29da624727494815348e775c Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Wed, 23 Aug 2023 21:09:53 -0300 Subject: [PATCH 071/134] fix(App.tsx): replace hardcoded error message with FetchErrorComponent to improve error handling and display feat(App.tsx): add support for displaying fetch error message and description in FetchErrorComponent feat(fetchErrorComponent): create FetchErrorComponent to display fetch error message and description fix(genericIconComponent): add stroke-width property to ensure consistent icon stroke width feat(loadingComponent): import LoadingComponentProps from types/components to improve type safety feat(constants): add FETCH_ERROR_MESSAGE and FETCH_ERROR_DESCRIPTION constants for fetch error handling fix(typesContext): remove console.log statement and set fetchError to true when an error occurs during fetching types feat(typesContext): add error handling for fetching types and set fetchError to true when an error occurs feat(typesContext): import fetchErrorComponentType from types/components to improve type safety feat(types/components): create fetchErrorComponentType and LoadingComponentProps interfaces for type safety fix(styleUtils): import Unplug icon from lucide-react to fix missing icon issue --- src/frontend/src/App.tsx | 10 +++++++++- .../src/components/fetchErrorComponent/index.tsx | 16 ++++++++++++++++ .../components/genericIconComponent/index.tsx | 8 +++++++- .../src/components/loadingComponent/index.tsx | 4 +--- src/frontend/src/constants/constants.ts | 4 ++++ src/frontend/src/contexts/typesContext.tsx | 1 - src/frontend/src/types/components/index.ts | 8 ++++++++ src/frontend/src/utils/styleUtils.ts | 2 ++ 8 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 src/frontend/src/components/fetchErrorComponent/index.tsx diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 5bda7ff37..2109222ed 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -9,7 +9,12 @@ import ErrorAlert from "./alerts/error"; import NoticeAlert from "./alerts/notice"; import SuccessAlert from "./alerts/success"; import CrashErrorComponent from "./components/CrashErrorComponent"; +import FetchErrorComponent from "./components/fetchErrorComponent"; import LoadingComponent from "./components/loadingComponent"; +import { + FETCH_ERROR_DESCRIPION, + FETCH_ERROR_MESSAGE, +} from "./constants/constants"; import { alertContext } from "./contexts/alertContext"; import { locationContext } from "./contexts/locationContext"; import { TabsContext } from "./contexts/tabsContext"; @@ -140,7 +145,10 @@ export default function App() { {loading ? ( {fetchError ? ( -There was an error on the backend+) : ( )} diff --git a/src/frontend/src/components/fetchErrorComponent/index.tsx b/src/frontend/src/components/fetchErrorComponent/index.tsx new file mode 100644 index 000000000..6004d9dfc --- /dev/null +++ b/src/frontend/src/components/fetchErrorComponent/index.tsx @@ -0,0 +1,16 @@ +import { fetchErrorComponentType } from "../../types/components"; +import IconComponent from "../genericIconComponent"; + +export default function FetchErrorComponent({ + message, + description, +}: fetchErrorComponentType) { + return ( + ++ ); +} diff --git a/src/frontend/src/components/genericIconComponent/index.tsx b/src/frontend/src/components/genericIconComponent/index.tsx index 32647f159..a06707d74 100644 --- a/src/frontend/src/components/genericIconComponent/index.tsx +++ b/src/frontend/src/components/genericIconComponent/index.tsx @@ -7,5 +7,11 @@ export default function IconComponent({ iconColor, }: IconComponentProps): JSX.Element { const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"]; - return+
+ {message} + {description} +; + return ( + + ); } diff --git a/src/frontend/src/components/loadingComponent/index.tsx b/src/frontend/src/components/loadingComponent/index.tsx index 31e78d474..a1e406710 100644 --- a/src/frontend/src/components/loadingComponent/index.tsx +++ b/src/frontend/src/components/loadingComponent/index.tsx @@ -1,6 +1,4 @@ -type LoadingComponentProps = { - remSize: number; -}; +import { LoadingComponentProps } from "../../types/components"; export default function LoadingComponent({ remSize }: LoadingComponentProps) { return ( diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 3f698c64e..aa9f1555a 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -511,3 +511,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [ ]; export const skipNodeUpdate = ["CustomComponent"]; + +export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection."; +export const FETCH_ERROR_DESCRIPION = + "Check if everything is working properly and try again."; diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx index 189f61af0..05346838d 100644 --- a/src/frontend/src/contexts/typesContext.tsx +++ b/src/frontend/src/contexts/typesContext.tsx @@ -78,7 +78,6 @@ export function TypesProvider({ children }: { children: ReactNode }) { } catch (error) { console.error("An error has occurred while fetching types."); await getHealth().catch((e) => { - console.log("entrou"); setFetchError(true); }); } diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 5cf87b8d3..0ebe768c1 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -171,3 +171,11 @@ export type IconComponentProps = { export interface languageMap { [key: string]: string | undefined; } +export type fetchErrorComponentType = { + message: string; + description: string; +}; + +export type LoadingComponentProps = { + remSize: number; +}; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 42fbe8773..ca100b5c3 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -56,6 +56,7 @@ import { TerminalSquare, Trash2, Undo, + Unplug, Upload, Users2, Variable, @@ -275,4 +276,5 @@ export const nodeIconsLucide = { Upload, MessageSquare, MoreHorizontal, + Unplug, }; From 130dc7ead6b084ac64a887633f4823e8770c72fd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 23 Aug 2023 21:12:11 -0300 Subject: [PATCH 072/134] =?UTF-8?q?=F0=9F=94=A7=20fix(schemas.py):=20impor?= =?UTF-8?q?t=20ApiKeyRead=20from=20api=5Fkey=20module=20to=20fix=20missing?= =?UTF-8?q?=20import=20error=20=F0=9F=94=A7=20fix(models/=5F=5Finit=5F=5F.?= =?UTF-8?q?py):=20add=20ApiKey=20to=20=5F=5Fall=5F=5F=20list=20to=20fix=20?= =?UTF-8?q?missing=20import=20error=20=E2=9C=A8=20feat(models/api=5Fkey.py?= =?UTF-8?q?):=20add=20ApiKey=20model=20and=20its=20related=20classes=20to?= =?UTF-8?q?=20support=20API=20key=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/schemas.py | 7 +++++ .../services/database/models/__init__.py | 3 +- .../services/database/models/api_key.py | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/backend/langflow/services/database/models/api_key.py diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 65bf64dca..d188e5c7a 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -1,6 +1,7 @@ from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Union +from langflow.services.database.models.api_key import ApiKeyRead from langflow.services.database.models.flow import FlowCreate, FlowRead from pydantic import BaseModel, Field, validator import json @@ -134,3 +135,9 @@ class ComponentListCreate(BaseModel): class ComponentListRead(BaseModel): flows: List[FlowRead] + + +class ApiKeyResponse(BaseModel): + total_count: int + user_id: str + api_keys: List[ApiKeyRead] diff --git a/src/backend/langflow/services/database/models/__init__.py b/src/backend/langflow/services/database/models/__init__.py index 28d2b4af8..01e81e277 100644 --- a/src/backend/langflow/services/database/models/__init__.py +++ b/src/backend/langflow/services/database/models/__init__.py @@ -1,5 +1,6 @@ from .flow import Flow from .user import User from .token import Token +from .api_key import ApiKey -__all__ = ["Flow", "User", "Token"] +__all__ = ["Flow", "User", "Token", "ApiKey"] diff --git a/src/backend/langflow/services/database/models/api_key.py b/src/backend/langflow/services/database/models/api_key.py new file mode 100644 index 000000000..3549c10c4 --- /dev/null +++ b/src/backend/langflow/services/database/models/api_key.py @@ -0,0 +1,28 @@ +from sqlmodel import Field +from uuid import UUID, uuid4 +from typing import Optional +from datetime import datetime +from langflow.services.database.models.base import SQLModelSerializable, SQLModel + + +class ApiKeyBase(SQLModelSerializable): + api_key: str = Field(index=True, unique=True) + name: str = Field() + create_at: datetime = Field(default_factory=datetime.utcnow) + last_used_at: Optional[datetime] = Field() + + +class ApiKey(ApiKeyBase, table=True): + id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) + + +class ApiKeyCreate(SQLModel): + name: str = Field() + + +class ApiKeyRead(SQLModel): + id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) + api_key: str = Field(index=True, unique=True) + name: str = Field() + create_at: datetime = Field(default_factory=datetime.utcnow) + last_used_at: Optional[datetime] = Field() From eca7a56f761e3fe9efe5d56fb9330fd00c05d8c9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 23 Aug 2023 21:30:43 -0300 Subject: [PATCH 073/134] =?UTF-8?q?=F0=9F=94=96=20chore(pyproject.toml):?= =?UTF-8?q?=20bump=20version=20from=200.4.13=20to=200.4.14=20for=20langflo?= =?UTF-8?q?w=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1475ef5ad..cc0d1a617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow" -version = "0.4.13" +version = "0.4.14" description = "A Python package with a built-in web application" authors = ["Logspace "] maintainers = [ From 63ca40850631a8a3b2c5f2234d70101c6095817e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 23 Aug 2023 21:43:14 -0300 Subject: [PATCH 074/134] =?UTF-8?q?=F0=9F=94=A7=20chore(alembic):=20add=20?= =?UTF-8?q?ApiKey=20table=20and=20remove=20FlowStyle=20and=20Component=20t?= =?UTF-8?q?ables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔄 refactor(alembic): downgrade migration to recreate FlowStyle and Component tables and remove User and ApiKey tables --- .../versions/5512e39b4012_add_apikey_table.py | 84 +++++++++++++++++++ .../services/database/models/api_key.py | 18 ++-- 2 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py diff --git a/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py b/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py new file mode 100644 index 000000000..8c8ae75fd --- /dev/null +++ b/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py @@ -0,0 +1,84 @@ +"""Add ApiKey table + +Revision ID: 5512e39b4012 +Revises: 0a534bdfd84b +Create Date: 2023-08-23 21:05:51.042203 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = '5512e39b4012' +down_revision: Union[str, None] = '0a534bdfd84b' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('apikey', + sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('create_at', sa.DateTime(), nullable=False), + sa.Column('last_used_at', sa.DateTime(), nullable=True), + sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + op.create_index(op.f('ix_apikey_api_key'), 'apikey', ['api_key'], unique=True) + op.create_table('user', + sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('password', sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('is_superuser', sa.Boolean(), nullable=False), + sa.Column('create_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.Column('last_login_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True) + op.drop_table('flowstyle') + op.drop_index('ix_component_frontend_node_id', table_name='component') + op.drop_index('ix_component_name', table_name='component') + op.drop_table('component') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('component', + sa.Column('id', sa.CHAR(length=32), nullable=False), + sa.Column('frontend_node_id', sa.CHAR(length=32), nullable=False), + sa.Column('name', sa.VARCHAR(), nullable=False), + sa.Column('description', sa.VARCHAR(), nullable=True), + sa.Column('python_code', sa.VARCHAR(), nullable=True), + sa.Column('return_type', sa.VARCHAR(), nullable=True), + sa.Column('is_disabled', sa.BOOLEAN(), nullable=False), + sa.Column('is_read_only', sa.BOOLEAN(), nullable=False), + sa.Column('create_at', sa.DATETIME(), nullable=False), + sa.Column('update_at', sa.DATETIME(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index('ix_component_name', 'component', ['name'], unique=False) + op.create_index('ix_component_frontend_node_id', 'component', ['frontend_node_id'], unique=False) + op.create_table('flowstyle', + sa.Column('color', sa.VARCHAR(), nullable=False), + sa.Column('emoji', sa.VARCHAR(), nullable=False), + sa.Column('flow_id', sa.CHAR(length=32), nullable=True), + sa.Column('id', sa.CHAR(length=32), nullable=False), + sa.ForeignKeyConstraint(['flow_id'], ['flow.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('id') + ) + op.drop_index(op.f('ix_user_username'), table_name='user') + op.drop_table('user') + op.drop_index(op.f('ix_apikey_api_key'), table_name='apikey') + op.drop_table('apikey') + # ### end Alembic commands ### diff --git a/src/backend/langflow/services/database/models/api_key.py b/src/backend/langflow/services/database/models/api_key.py index 3549c10c4..784b25229 100644 --- a/src/backend/langflow/services/database/models/api_key.py +++ b/src/backend/langflow/services/database/models/api_key.py @@ -2,27 +2,23 @@ from sqlmodel import Field from uuid import UUID, uuid4 from typing import Optional from datetime import datetime -from langflow.services.database.models.base import SQLModelSerializable, SQLModel +from langflow.services.database.models.base import SQLModelSerializable class ApiKeyBase(SQLModelSerializable): api_key: str = Field(index=True, unique=True) - name: str = Field() + name: Optional[str] = Field(index=True) create_at: datetime = Field(default_factory=datetime.utcnow) - last_used_at: Optional[datetime] = Field() + last_used_at: Optional[datetime] = Field(default=None) class ApiKey(ApiKeyBase, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) -class ApiKeyCreate(SQLModel): - name: str = Field() +class ApiKeyCreate(ApiKeyBase): + pass -class ApiKeyRead(SQLModel): - id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) - api_key: str = Field(index=True, unique=True) - name: str = Field() - create_at: datetime = Field(default_factory=datetime.utcnow) - last_used_at: Optional[datetime] = Field() +class ApiKeyRead(ApiKeyBase): + id: UUID From fd6ef1815b964839d07e118aef77883c19ef44eb Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 23 Aug 2023 21:43:23 -0300 Subject: [PATCH 075/134] format --- docker-compose.debug.yml | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml index 6a9802e38..c73ff2a60 100644 --- a/docker-compose.debug.yml +++ b/docker-compose.debug.yml @@ -1,33 +1,33 @@ -version: "3.4" - -services: - backend: - volumes: - - ./:/app - build: - context: ./ - dockerfile: ./dev.Dockerfile - command: - [ - "sh", - "-c", - "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload", - ] - ports: - - 7860:7860 - - 5678:5678 - restart: on-failure - - frontend: - build: - context: ./src/frontend - dockerfile: ./dev.Dockerfile - args: - - BACKEND_URL=http://backend:7860 - ports: - - "3000:3000" - volumes: - - ./src/frontend/public:/home/node/app/public - - ./src/frontend/src:/home/node/app/src - - ./src/frontend/package.json:/home/node/app/package.json - restart: on-failure +version: "3.4" + +services: + backend: + volumes: + - ./:/app + build: + context: ./ + dockerfile: ./dev.Dockerfile + command: + [ + "sh", + "-c", + "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload", + ] + ports: + - 7860:7860 + - 5678:5678 + restart: on-failure + + frontend: + build: + context: ./src/frontend + dockerfile: ./dev.Dockerfile + args: + - BACKEND_URL=http://backend:7860 + ports: + - "3000:3000" + volumes: + - ./src/frontend/public:/home/node/app/public + - ./src/frontend/src:/home/node/app/src + - ./src/frontend/package.json:/home/node/app/package.json + restart: on-failure From cdd25dbeb215fe9a8751f6f048dc5123f7717d65 Mon Sep 17 00:00:00 2001 From: DiogenesBR Date: Thu, 24 Aug 2023 14:55:40 +0000 Subject: [PATCH 076/134] add some new types to the .gitattributes --- .gitattributes | 4 + .githooks/pre-commit | 0 docs/static/data/organizations-100.csv | 202 ++++++++++++------------- docs/static/img/logo.svg | Bin 32058 -> 32057 bytes src/frontend/set_proxy.sh | 0 5 files changed, 105 insertions(+), 101 deletions(-) mode change 100755 => 100644 .githooks/pre-commit mode change 100755 => 100644 src/frontend/set_proxy.sh diff --git a/.gitattributes b/.gitattributes index aa545d82f..616cbc4c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,10 @@ *.svg text *.yml text *.yaml text +*.xml text +*.csv text +*.json text +*.sh text *.Dockerfile text Dockerfile text diff --git a/.githooks/pre-commit b/.githooks/pre-commit old mode 100755 new mode 100644 diff --git a/docs/static/data/organizations-100.csv b/docs/static/data/organizations-100.csv index 93dcac9f3..a111992d7 100644 --- a/docs/static/data/organizations-100.csv +++ b/docs/static/data/organizations-100.csv @@ -1,101 +1,101 @@ -Index,Organization Id,Name,Website,Country,Description,Founded,Industry,Number of employees -1,FAB0d41d5b5d22c,Ferrell LLC,https://price.net/,Papua New Guinea,Horizontal empowering knowledgebase,1990,Plastics,3498 -2,6A7EdDEA9FaDC52,"Mckinney, Riley and Day",http://www.hall-buchanan.info/,Finland,User-centric system-worthy leverage,2015,Glass / Ceramics / Concrete,4952 -3,0bFED1ADAE4bcC1,Hester Ltd,http://sullivan-reed.com/,China,Switchable scalable moratorium,1971,Public Safety,5287 -4,2bFC1Be8a4ce42f,Holder-Sellers,https://becker.com/,Turkmenistan,De-engineered systemic artificial intelligence,2004,Automotive,921 -5,9eE8A6a4Eb96C24,Mayer Group,http://www.brewer.com/,Mauritius,Synchronized needs-based challenge,1991,Transportation,7870 -6,cC757116fe1C085,Henry-Thompson,http://morse.net/,Bahamas,Face-to-face well-modulated customer loyalty,1992,Primary / Secondary Education,4914 -7,219233e8aFF1BC3,Hansen-Everett,https://www.kidd.org/,Pakistan,Seamless disintermediate collaboration,2018,Publishing Industry,7832 -8,ccc93DCF81a31CD,Mcintosh-Mora,https://www.brooks.com/,Heard Island and McDonald Islands,Centralized attitude-oriented capability,1970,Import / Export,4389 -9,0B4F93aA06ED03e,Carr Inc,http://ross.com/,Kuwait,Distributed impactful customer loyalty,1996,Plastics,8167 -10,738b5aDe6B1C6A5,Gaines Inc,http://sandoval-hooper.com/,Uzbekistan,Multi-lateral scalable protocol,1997,Outsourcing / Offshoring,9698 -11,AE61b8Ffebbc476,Kidd Group,http://www.lyons.com/,Bouvet Island (Bouvetoya),Proactive foreground paradigm,2001,Primary / Secondary Education,7473 -12,eb3B7D06cCdD609,Crane-Clarke,https://www.sandoval.com/,Denmark,Front-line clear-thinking encryption,2014,Food / Beverages,9011 -13,8D0c29189C9798B,"Keller, Campos and Black",https://www.garner.info/,Liberia,Ameliorated directional emulation,2020,Museums / Institutions,2862 -14,D2c91cc03CA394c,Glover-Pope,http://www.silva.biz/,United Arab Emirates,Persevering contextually-based approach,2013,Medical Practice,9079 -15,C8AC1eaf9C036F4,Pacheco-Spears,https://aguilar.com/,Sweden,Secured logistical synergy,1984,Maritime,769 -16,b5D10A14f7a8AfE,Hodge-Ayers,http://www.archer-elliott.com/,Honduras,Future-proofed radical implementation,1990,Facilities Services,8508 -17,68139b5C4De03B4,"Bowers, Guerra and Krause",http://www.carrillo-nicholson.com/,Uganda,De-engineered transitional strategy,1972,Primary / Secondary Education,6986 -18,5c2EffEfdba2BdF,Mckenzie-Melton,http://montoya-thompson.com/,Hong Kong,Reverse-engineered heuristic alliance,1998,Investment Management / Hedge Fund / Private Equity,4589 -19,ba179F19F7925f5,Branch-Mann,http://www.lozano.com/,Botswana,Adaptive intangible frame,1999,Architecture / Planning,7961 -20,c1Ce9B350BAc66b,Weiss and Sons,https://barrett.com/,Korea,Sharable optimal functionalities,2011,Plastics,5984 -21,8de40AC4e6EaCa4,"Velez, Payne and Coffey",http://burton.com/,Luxembourg,Mandatory coherent synergy,1986,Wholesale,5010 -22,Aad86a4F0385F2d,Harrell LLC,http://www.frey-rosario.com/,Guadeloupe,Reverse-engineered mission-critical moratorium,2018,Construction,2185 -23,22aC3FFd64fD703,"Eaton, Reynolds and Vargas",http://www.freeman.biz/,Monaco,Self-enabling multi-tasking process improvement,2014,Luxury Goods / Jewelry,8987 -24,5Ec4C272bCf085c,Robbins-Cummings,http://donaldson-wilkins.com/,Belgium,Organic non-volatile hierarchy,1991,Pharmaceuticals,5038 -25,5fDBeA8BB91a000,Jenkins Inc,http://www.kirk.biz/,South Africa,Front-line systematic help-desk,2002,Insurance,1215 -26,dFfD6a6F9AC2d9C,"Greene, Benjamin and Novak",http://www.kent.net/,Romania,Centralized leadingedge moratorium,2012,Museums / Institutions,4941 -27,4B217cC5a0674C5,"Dickson, Richmond and Clay",http://everett.com/,Czech Republic,Team-oriented tangible complexity,1980,Real Estate / Mortgage,3122 -28,88b1f1cDcf59a37,Prince-David,http://thompson.com/,Christmas Island,Virtual holistic methodology,1970,Banking / Mortgage,1046 -29,f9F7bBCAEeC360F,Ayala LLC,http://www.zhang.com/,Philippines,Open-source zero administration hierarchy,2021,Legal Services,7664 -30,7Cb3AeFcE4Ba31e,Rivas Group,https://hebert.org/,Australia,Open-architected well-modulated capacity,1998,Logistics / Procurement,4155 -31,ccBcC32adcbc530,"Sloan, Mays and Whitehead",http://lawson.com/,Chad,Face-to-face high-level conglomeration,1997,Civil Engineering,365 -32,f5afd686b3d05F5,"Durham, Allen and Barnes",http://chan-stafford.org/,Zimbabwe,Synergistic web-enabled framework,1993,Mechanical or Industrial Engineering,6135 -33,38C6cfC5074Fa5e,Fritz-Franklin,http://www.lambert.com/,Nepal,Automated 4thgeneration website,1972,Hospitality,4516 -34,5Cd7efccCcba38f,Burch-Ewing,http://cline.net/,Taiwan,User-centric 4thgeneration system engine,1981,Venture Capital / VC,7443 -35,9E6Acb51e3F9d6F,"Glass, Barrera and Turner",https://dunlap.com/,Kyrgyz Republic,Multi-channeled 3rdgeneration open system,2020,Utilities,2610 -36,4D4d7E18321eaeC,Pineda-Cox,http://aguilar.org/,Bolivia,Fundamental asynchronous capability,2010,Human Resources / HR,1312 -37,485f5d06B938F2b,"Baker, Mccann and Macdonald",http://www.anderson-barker.com/,Kenya,Cross-group user-facing focus group,2013,Legislative Office,1638 -38,19E3a5Bf6dBDc4F,Cuevas-Moss,https://dodson-castaneda.net/,Guatemala,Extended human-resource intranet,1994,Music,9995 -39,6883A965c7b68F7,Hahn PLC,http://newman.com/,Belarus,Organic logistical leverage,2012,Electrical / Electronic Manufacturing,3715 -40,AC5B7AA74Aa4A2E,"Valentine, Ferguson and Kramer",http://stuart.net/,Jersey,Centralized secondary time-frame,1997,Non - Profit / Volunteering,3585 -41,decab0D5027CA6a,Arroyo Inc,https://www.turner.com/,Grenada,Managed demand-driven website,2006,Writing / Editing,9067 -42,dF084FbBb613eea,Walls LLC,http://www.reese-vasquez.biz/,Cape Verde,Self-enabling fresh-thinking installation,1989,Investment Management / Hedge Fund / Private Equity,1678 -43,A2D89Ab9bCcAd4e,"Mitchell, Warren and Schneider",https://fox.biz/,Trinidad and Tobago,Enhanced intangible time-frame,2021,Capital Markets / Hedge Fund / Private Equity,3816 -44,77aDc905434a49f,Prince PLC,https://www.watts.com/,Sweden,Profit-focused coherent installation,2016,Individual / Family Services,7645 -45,235fdEFE2cfDa5F,Brock-Blackwell,http://www.small.com/,Benin,Secured foreground emulation,1986,Online Publishing,7034 -46,1eD64cFe986BBbE,Walton-Barnett,https://ashley-schaefer.com/,Western Sahara,Right-sized clear-thinking flexibility,2001,Luxury Goods / Jewelry,1746 -47,CbBbFcdd0eaE2cF,Bartlett-Arroyo,https://cruz.com/,Northern Mariana Islands,Realigned didactic function,1976,Civic / Social Organization,3987 -48,49aECbDaE6aBD53,"Wallace, Madden and Morris",http://www.blevins-fernandez.biz/,Germany,Persistent real-time customer loyalty,2016,Pharmaceuticals,9443 -49,7b3fe6e7E72bFa4,Berg-Sparks,https://cisneros-love.com/,Canada,Stand-alone static implementation,1974,Arts / Crafts,2073 -50,c6DedA82A8aef7E,Gonzales Ltd,http://bird.com/,Tonga,Managed human-resource policy,1988,Consumer Goods,9069 -51,7D9FBF85cdC3871,Lawson and Sons,https://www.wong.com/,French Southern Territories,Compatible analyzing intranet,2021,Arts / Crafts,3527 -52,7dd18Fb7cB07b65,"Mcguire, Mcconnell and Olsen",https://melton-briggs.com/,Korea,Profound client-server frame,1988,Printing,8445 -53,EF5B55FadccB8Fe,Charles-Phillips,https://bowman.com/,Cote d'Ivoire,Monitored client-server implementation,2012,Mental Health Care,3450 -54,f8D4B99e11fAF5D,Odom Ltd,https://www.humphrey-hess.com/,Cote d'Ivoire,Advanced static process improvement,2012,Management Consulting,1825 -55,e24D21BFd3bF1E5,Richard PLC,https://holden-coleman.net/,Mayotte,Object-based optimizing model,1971,Broadcast Media,4942 -56,B9BdfEB6D3Ca44E,Sampson Ltd,https://blevins.com/,Cayman Islands,Intuitive local adapter,2005,Farming,1418 -57,2a74D6f3D3B268e,"Cherry, Le and Callahan",https://waller-delacruz.biz/,Nigeria,Universal human-resource collaboration,2017,Entertainment / Movie Production,7202 -58,Bf3F3f62c8aBC33,Cherry PLC,https://www.avila.info/,Marshall Islands,Persistent tertiary website,1980,Plastics,8245 -59,aeBe26B80a7a23c,Melton-Nichols,https://kennedy.com/,Palau,User-friendly clear-thinking productivity,2021,Legislative Office,8741 -60,aAeb29ad43886C6,Potter-Walsh,http://thomas-french.org/,Turkey,Optional non-volatile open system,2008,Human Resources / HR,6923 -61,bD1bc6bB6d1FeD3,Freeman-Chen,https://mathis.com/,Timor-Leste,Phased next generation adapter,1973,International Trade / Development,346 -62,EB9f456e8b7022a,Soto Group,https://norris.info/,Vietnam,Enterprise-wide executive installation,1988,Business Supplies / Equipment,9097 -63,Dfef38C51D8DAe3,"Poole, Cruz and Whitney",https://reed.info/,Reunion,Balanced analyzing groupware,1978,Marketing / Advertising / Sales,2992 -64,055ffEfB2Dd95B0,Riley Ltd,http://wiley.com/,Brazil,Optional exuding superstructure,1986,Textiles,9315 -65,cBfe4dbAE1699da,"Erickson, Andrews and Bailey",https://www.hobbs-grant.com/,Eritrea,Vision-oriented secondary project,2014,Consumer Electronics,7829 -66,fdFbecbadcdCdf1,"Wilkinson, Charles and Arroyo",http://hunter-mcfarland.com/,United States Virgin Islands,Assimilated 24/7 archive,1996,Building Materials,602 -67,5DCb8A5a5ca03c0,Floyd Ltd,http://www.whitney.com/,Falkland Islands (Malvinas),Function-based fault-tolerant concept,2017,Public Relations / PR,2911 -68,ce57DCbcFD6d618,Newman-Galloway,https://www.scott.com/,Luxembourg,Enhanced foreground collaboration,1987,Information Technology / IT,3934 -69,5aaD187dc929371,Frazier-Butler,https://www.daugherty-farley.info/,Northern Mariana Islands,Persistent interactive circuit,1972,Outsourcing / Offshoring,5130 -70,902D7Ac8b6d476b,Newton Inc,https://www.richmond-manning.info/,Netherlands Antilles,Fundamental stable info-mediaries,1976,Military Industry,563 -71,32BB9Ff4d939788,Duffy-Levy,https://www.potter.com/,Guernsey,Diverse exuding installation,1982,Wireless,6146 -72,adcB0afbE58bAe3,Wagner LLC,https://decker-esparza.com/,Uruguay,Reactive attitude-oriented toolset,1987,International Affairs,6874 -73,dfcA1c84AdB61Ac,Mccall-Holmes,http://www.dean.com/,Benin,Object-based value-added database,2009,Legal Services,696 -74,208044AC2fe52F3,Massey LLC,https://frazier.biz/,Suriname,Configurable zero administration Graphical User Interface,1986,Accounting,5004 -75,f3C365f0c1A0623,Hicks LLC,http://alvarez.biz/,Pakistan,Quality-focused client-server Graphical User Interface,1970,Computer Software / Engineering,8480 -76,ec5Bdd3CBAfaB93,"Cole, Russell and Avery",http://www.blankenship.com/,Mongolia,De-engineered fault-tolerant challenge,2000,Law Enforcement,7012 -77,DDB19Be7eeB56B4,Cummings-Rojas,https://simon-pearson.com/,Svalbard & Jan Mayen Islands,User-centric modular customer loyalty,2012,Financial Services,7529 -78,dd6CA3d0bc3cAfc,"Beasley, Greene and Mahoney",http://www.petersen-lawrence.com/,Togo,Extended content-based methodology,1976,Religious Institutions,869 -79,A0B9d56e61070e3,"Beasley, Sims and Allison",http://burke.info/,Latvia,Secured zero tolerance hub,1972,Facilities Services,6182 -80,cBa7EFe5D05Adaf,Crawford-Rivera,https://black-ramirez.org/,Cuba,Persevering exuding budgetary management,1999,Online Publishing,7805 -81,Ea3f6D52Ec73563,Montes-Hensley,https://krueger.org/,Liechtenstein,Multi-tiered secondary productivity,2009,Printing,8433 -82,bC0CEd48A8000E0,Velazquez-Odom,https://stokes.com/,Djibouti,Streamlined 6thgeneration function,2002,Alternative Dispute Resolution,4044 -83,c89b9b59BC4baa1,Eaton-Morales,https://www.reeves-graham.com/,Micronesia,Customer-focused explicit frame,1990,Capital Markets / Hedge Fund / Private Equity,7013 -84,FEC51bce8421a7b,"Roberson, Pennington and Palmer",http://www.keith-fisher.com/,Cameroon,Adaptive bi-directional hierarchy,1993,Telecommunications,5571 -85,e0E8e27eAc9CAd5,"George, Russo and Guerra",https://drake.com/,Sweden,Centralized non-volatile capability,1989,Military Industry,2880 -86,B97a6CF9bf5983C,Davila Inc,https://mcconnell.info/,Cocos (Keeling) Islands,Profit-focused dedicated frame,2017,Consumer Electronics,2215 -87,a0a6f9b3DbcBEb5,Mays-Preston,http://www.browning-key.com/,Mali,User-centric heuristic focus group,2006,Military Industry,5786 -88,8cC1bDa330a5871,Pineda-Morton,https://www.carr.com/,United States Virgin Islands,Grass-roots methodical info-mediaries,1991,Printing,6168 -89,ED889CB2FE9cbd3,Huang and Sons,https://www.bolton.com/,Eritrea,Re-contextualized dynamic hierarchy,1981,Semiconductors,7484 -90,F4Dc1417BC6cb8f,Gilbert-Simon,https://www.bradford.biz/,Burundi,Grass-roots radical parallelism,1973,Newspapers / Journalism,1927 -91,7ABc3c7ecA03B34,Sampson-Griffith,http://hendricks.org/,Benin,Multi-layered composite paradigm,1972,Textiles,3881 -92,4e0719FBE38e0aB,Miles-Dominguez,http://www.turner.com/,Gibraltar,Organized empowering forecast,1996,Civic / Social Organization,897 -93,dEbDAAeDfaed00A,Rowe and Sons,https://www.simpson.org/,El Salvador,Balanced multimedia knowledgebase,1978,Facilities Services,8172 -94,61BDeCfeFD0cEF5,"Valenzuela, Holmes and Rowland",https://www.dorsey.net/,Taiwan,Persistent tertiary focus group,1999,Transportation,1483 -95,4e91eD25f486110,"Best, Wade and Shepard",https://zimmerman.com/,Zimbabwe,Innovative background definition,1991,Gambling / Casinos,4873 -96,0a0bfFbBbB8eC7c,Holmes Group,https://mcdowell.org/,Ethiopia,Right-sized zero tolerance focus group,1975,Photography,2988 -97,BA6Cd9Dae2Efd62,Good Ltd,http://duffy.com/,Anguilla,Reverse-engineered composite moratorium,1971,Consumer Services,4292 -98,E7df80C60Abd7f9,Clements-Espinoza,http://www.flowers.net/,Falkland Islands (Malvinas),Progressive modular hub,1991,Broadcast Media,236 -99,AFc285dbE2fEd24,Mendez Inc,https://www.burke.net/,Kyrgyz Republic,User-friendly exuding migration,1993,Education Management,339 -100,e9eB5A60Cef8354,Watkins-Kaiser,http://www.herring.com/,Togo,Synergistic background access,2009,Financial Services,2785 +Index,Organization Id,Name,Website,Country,Description,Founded,Industry,Number of employees +1,FAB0d41d5b5d22c,Ferrell LLC,https://price.net/,Papua New Guinea,Horizontal empowering knowledgebase,1990,Plastics,3498 +2,6A7EdDEA9FaDC52,"Mckinney, Riley and Day",http://www.hall-buchanan.info/,Finland,User-centric system-worthy leverage,2015,Glass / Ceramics / Concrete,4952 +3,0bFED1ADAE4bcC1,Hester Ltd,http://sullivan-reed.com/,China,Switchable scalable moratorium,1971,Public Safety,5287 +4,2bFC1Be8a4ce42f,Holder-Sellers,https://becker.com/,Turkmenistan,De-engineered systemic artificial intelligence,2004,Automotive,921 +5,9eE8A6a4Eb96C24,Mayer Group,http://www.brewer.com/,Mauritius,Synchronized needs-based challenge,1991,Transportation,7870 +6,cC757116fe1C085,Henry-Thompson,http://morse.net/,Bahamas,Face-to-face well-modulated customer loyalty,1992,Primary / Secondary Education,4914 +7,219233e8aFF1BC3,Hansen-Everett,https://www.kidd.org/,Pakistan,Seamless disintermediate collaboration,2018,Publishing Industry,7832 +8,ccc93DCF81a31CD,Mcintosh-Mora,https://www.brooks.com/,Heard Island and McDonald Islands,Centralized attitude-oriented capability,1970,Import / Export,4389 +9,0B4F93aA06ED03e,Carr Inc,http://ross.com/,Kuwait,Distributed impactful customer loyalty,1996,Plastics,8167 +10,738b5aDe6B1C6A5,Gaines Inc,http://sandoval-hooper.com/,Uzbekistan,Multi-lateral scalable protocol,1997,Outsourcing / Offshoring,9698 +11,AE61b8Ffebbc476,Kidd Group,http://www.lyons.com/,Bouvet Island (Bouvetoya),Proactive foreground paradigm,2001,Primary / Secondary Education,7473 +12,eb3B7D06cCdD609,Crane-Clarke,https://www.sandoval.com/,Denmark,Front-line clear-thinking encryption,2014,Food / Beverages,9011 +13,8D0c29189C9798B,"Keller, Campos and Black",https://www.garner.info/,Liberia,Ameliorated directional emulation,2020,Museums / Institutions,2862 +14,D2c91cc03CA394c,Glover-Pope,http://www.silva.biz/,United Arab Emirates,Persevering contextually-based approach,2013,Medical Practice,9079 +15,C8AC1eaf9C036F4,Pacheco-Spears,https://aguilar.com/,Sweden,Secured logistical synergy,1984,Maritime,769 +16,b5D10A14f7a8AfE,Hodge-Ayers,http://www.archer-elliott.com/,Honduras,Future-proofed radical implementation,1990,Facilities Services,8508 +17,68139b5C4De03B4,"Bowers, Guerra and Krause",http://www.carrillo-nicholson.com/,Uganda,De-engineered transitional strategy,1972,Primary / Secondary Education,6986 +18,5c2EffEfdba2BdF,Mckenzie-Melton,http://montoya-thompson.com/,Hong Kong,Reverse-engineered heuristic alliance,1998,Investment Management / Hedge Fund / Private Equity,4589 +19,ba179F19F7925f5,Branch-Mann,http://www.lozano.com/,Botswana,Adaptive intangible frame,1999,Architecture / Planning,7961 +20,c1Ce9B350BAc66b,Weiss and Sons,https://barrett.com/,Korea,Sharable optimal functionalities,2011,Plastics,5984 +21,8de40AC4e6EaCa4,"Velez, Payne and Coffey",http://burton.com/,Luxembourg,Mandatory coherent synergy,1986,Wholesale,5010 +22,Aad86a4F0385F2d,Harrell LLC,http://www.frey-rosario.com/,Guadeloupe,Reverse-engineered mission-critical moratorium,2018,Construction,2185 +23,22aC3FFd64fD703,"Eaton, Reynolds and Vargas",http://www.freeman.biz/,Monaco,Self-enabling multi-tasking process improvement,2014,Luxury Goods / Jewelry,8987 +24,5Ec4C272bCf085c,Robbins-Cummings,http://donaldson-wilkins.com/,Belgium,Organic non-volatile hierarchy,1991,Pharmaceuticals,5038 +25,5fDBeA8BB91a000,Jenkins Inc,http://www.kirk.biz/,South Africa,Front-line systematic help-desk,2002,Insurance,1215 +26,dFfD6a6F9AC2d9C,"Greene, Benjamin and Novak",http://www.kent.net/,Romania,Centralized leadingedge moratorium,2012,Museums / Institutions,4941 +27,4B217cC5a0674C5,"Dickson, Richmond and Clay",http://everett.com/,Czech Republic,Team-oriented tangible complexity,1980,Real Estate / Mortgage,3122 +28,88b1f1cDcf59a37,Prince-David,http://thompson.com/,Christmas Island,Virtual holistic methodology,1970,Banking / Mortgage,1046 +29,f9F7bBCAEeC360F,Ayala LLC,http://www.zhang.com/,Philippines,Open-source zero administration hierarchy,2021,Legal Services,7664 +30,7Cb3AeFcE4Ba31e,Rivas Group,https://hebert.org/,Australia,Open-architected well-modulated capacity,1998,Logistics / Procurement,4155 +31,ccBcC32adcbc530,"Sloan, Mays and Whitehead",http://lawson.com/,Chad,Face-to-face high-level conglomeration,1997,Civil Engineering,365 +32,f5afd686b3d05F5,"Durham, Allen and Barnes",http://chan-stafford.org/,Zimbabwe,Synergistic web-enabled framework,1993,Mechanical or Industrial Engineering,6135 +33,38C6cfC5074Fa5e,Fritz-Franklin,http://www.lambert.com/,Nepal,Automated 4thgeneration website,1972,Hospitality,4516 +34,5Cd7efccCcba38f,Burch-Ewing,http://cline.net/,Taiwan,User-centric 4thgeneration system engine,1981,Venture Capital / VC,7443 +35,9E6Acb51e3F9d6F,"Glass, Barrera and Turner",https://dunlap.com/,Kyrgyz Republic,Multi-channeled 3rdgeneration open system,2020,Utilities,2610 +36,4D4d7E18321eaeC,Pineda-Cox,http://aguilar.org/,Bolivia,Fundamental asynchronous capability,2010,Human Resources / HR,1312 +37,485f5d06B938F2b,"Baker, Mccann and Macdonald",http://www.anderson-barker.com/,Kenya,Cross-group user-facing focus group,2013,Legislative Office,1638 +38,19E3a5Bf6dBDc4F,Cuevas-Moss,https://dodson-castaneda.net/,Guatemala,Extended human-resource intranet,1994,Music,9995 +39,6883A965c7b68F7,Hahn PLC,http://newman.com/,Belarus,Organic logistical leverage,2012,Electrical / Electronic Manufacturing,3715 +40,AC5B7AA74Aa4A2E,"Valentine, Ferguson and Kramer",http://stuart.net/,Jersey,Centralized secondary time-frame,1997,Non - Profit / Volunteering,3585 +41,decab0D5027CA6a,Arroyo Inc,https://www.turner.com/,Grenada,Managed demand-driven website,2006,Writing / Editing,9067 +42,dF084FbBb613eea,Walls LLC,http://www.reese-vasquez.biz/,Cape Verde,Self-enabling fresh-thinking installation,1989,Investment Management / Hedge Fund / Private Equity,1678 +43,A2D89Ab9bCcAd4e,"Mitchell, Warren and Schneider",https://fox.biz/,Trinidad and Tobago,Enhanced intangible time-frame,2021,Capital Markets / Hedge Fund / Private Equity,3816 +44,77aDc905434a49f,Prince PLC,https://www.watts.com/,Sweden,Profit-focused coherent installation,2016,Individual / Family Services,7645 +45,235fdEFE2cfDa5F,Brock-Blackwell,http://www.small.com/,Benin,Secured foreground emulation,1986,Online Publishing,7034 +46,1eD64cFe986BBbE,Walton-Barnett,https://ashley-schaefer.com/,Western Sahara,Right-sized clear-thinking flexibility,2001,Luxury Goods / Jewelry,1746 +47,CbBbFcdd0eaE2cF,Bartlett-Arroyo,https://cruz.com/,Northern Mariana Islands,Realigned didactic function,1976,Civic / Social Organization,3987 +48,49aECbDaE6aBD53,"Wallace, Madden and Morris",http://www.blevins-fernandez.biz/,Germany,Persistent real-time customer loyalty,2016,Pharmaceuticals,9443 +49,7b3fe6e7E72bFa4,Berg-Sparks,https://cisneros-love.com/,Canada,Stand-alone static implementation,1974,Arts / Crafts,2073 +50,c6DedA82A8aef7E,Gonzales Ltd,http://bird.com/,Tonga,Managed human-resource policy,1988,Consumer Goods,9069 +51,7D9FBF85cdC3871,Lawson and Sons,https://www.wong.com/,French Southern Territories,Compatible analyzing intranet,2021,Arts / Crafts,3527 +52,7dd18Fb7cB07b65,"Mcguire, Mcconnell and Olsen",https://melton-briggs.com/,Korea,Profound client-server frame,1988,Printing,8445 +53,EF5B55FadccB8Fe,Charles-Phillips,https://bowman.com/,Cote d'Ivoire,Monitored client-server implementation,2012,Mental Health Care,3450 +54,f8D4B99e11fAF5D,Odom Ltd,https://www.humphrey-hess.com/,Cote d'Ivoire,Advanced static process improvement,2012,Management Consulting,1825 +55,e24D21BFd3bF1E5,Richard PLC,https://holden-coleman.net/,Mayotte,Object-based optimizing model,1971,Broadcast Media,4942 +56,B9BdfEB6D3Ca44E,Sampson Ltd,https://blevins.com/,Cayman Islands,Intuitive local adapter,2005,Farming,1418 +57,2a74D6f3D3B268e,"Cherry, Le and Callahan",https://waller-delacruz.biz/,Nigeria,Universal human-resource collaboration,2017,Entertainment / Movie Production,7202 +58,Bf3F3f62c8aBC33,Cherry PLC,https://www.avila.info/,Marshall Islands,Persistent tertiary website,1980,Plastics,8245 +59,aeBe26B80a7a23c,Melton-Nichols,https://kennedy.com/,Palau,User-friendly clear-thinking productivity,2021,Legislative Office,8741 +60,aAeb29ad43886C6,Potter-Walsh,http://thomas-french.org/,Turkey,Optional non-volatile open system,2008,Human Resources / HR,6923 +61,bD1bc6bB6d1FeD3,Freeman-Chen,https://mathis.com/,Timor-Leste,Phased next generation adapter,1973,International Trade / Development,346 +62,EB9f456e8b7022a,Soto Group,https://norris.info/,Vietnam,Enterprise-wide executive installation,1988,Business Supplies / Equipment,9097 +63,Dfef38C51D8DAe3,"Poole, Cruz and Whitney",https://reed.info/,Reunion,Balanced analyzing groupware,1978,Marketing / Advertising / Sales,2992 +64,055ffEfB2Dd95B0,Riley Ltd,http://wiley.com/,Brazil,Optional exuding superstructure,1986,Textiles,9315 +65,cBfe4dbAE1699da,"Erickson, Andrews and Bailey",https://www.hobbs-grant.com/,Eritrea,Vision-oriented secondary project,2014,Consumer Electronics,7829 +66,fdFbecbadcdCdf1,"Wilkinson, Charles and Arroyo",http://hunter-mcfarland.com/,United States Virgin Islands,Assimilated 24/7 archive,1996,Building Materials,602 +67,5DCb8A5a5ca03c0,Floyd Ltd,http://www.whitney.com/,Falkland Islands (Malvinas),Function-based fault-tolerant concept,2017,Public Relations / PR,2911 +68,ce57DCbcFD6d618,Newman-Galloway,https://www.scott.com/,Luxembourg,Enhanced foreground collaboration,1987,Information Technology / IT,3934 +69,5aaD187dc929371,Frazier-Butler,https://www.daugherty-farley.info/,Northern Mariana Islands,Persistent interactive circuit,1972,Outsourcing / Offshoring,5130 +70,902D7Ac8b6d476b,Newton Inc,https://www.richmond-manning.info/,Netherlands Antilles,Fundamental stable info-mediaries,1976,Military Industry,563 +71,32BB9Ff4d939788,Duffy-Levy,https://www.potter.com/,Guernsey,Diverse exuding installation,1982,Wireless,6146 +72,adcB0afbE58bAe3,Wagner LLC,https://decker-esparza.com/,Uruguay,Reactive attitude-oriented toolset,1987,International Affairs,6874 +73,dfcA1c84AdB61Ac,Mccall-Holmes,http://www.dean.com/,Benin,Object-based value-added database,2009,Legal Services,696 +74,208044AC2fe52F3,Massey LLC,https://frazier.biz/,Suriname,Configurable zero administration Graphical User Interface,1986,Accounting,5004 +75,f3C365f0c1A0623,Hicks LLC,http://alvarez.biz/,Pakistan,Quality-focused client-server Graphical User Interface,1970,Computer Software / Engineering,8480 +76,ec5Bdd3CBAfaB93,"Cole, Russell and Avery",http://www.blankenship.com/,Mongolia,De-engineered fault-tolerant challenge,2000,Law Enforcement,7012 +77,DDB19Be7eeB56B4,Cummings-Rojas,https://simon-pearson.com/,Svalbard & Jan Mayen Islands,User-centric modular customer loyalty,2012,Financial Services,7529 +78,dd6CA3d0bc3cAfc,"Beasley, Greene and Mahoney",http://www.petersen-lawrence.com/,Togo,Extended content-based methodology,1976,Religious Institutions,869 +79,A0B9d56e61070e3,"Beasley, Sims and Allison",http://burke.info/,Latvia,Secured zero tolerance hub,1972,Facilities Services,6182 +80,cBa7EFe5D05Adaf,Crawford-Rivera,https://black-ramirez.org/,Cuba,Persevering exuding budgetary management,1999,Online Publishing,7805 +81,Ea3f6D52Ec73563,Montes-Hensley,https://krueger.org/,Liechtenstein,Multi-tiered secondary productivity,2009,Printing,8433 +82,bC0CEd48A8000E0,Velazquez-Odom,https://stokes.com/,Djibouti,Streamlined 6thgeneration function,2002,Alternative Dispute Resolution,4044 +83,c89b9b59BC4baa1,Eaton-Morales,https://www.reeves-graham.com/,Micronesia,Customer-focused explicit frame,1990,Capital Markets / Hedge Fund / Private Equity,7013 +84,FEC51bce8421a7b,"Roberson, Pennington and Palmer",http://www.keith-fisher.com/,Cameroon,Adaptive bi-directional hierarchy,1993,Telecommunications,5571 +85,e0E8e27eAc9CAd5,"George, Russo and Guerra",https://drake.com/,Sweden,Centralized non-volatile capability,1989,Military Industry,2880 +86,B97a6CF9bf5983C,Davila Inc,https://mcconnell.info/,Cocos (Keeling) Islands,Profit-focused dedicated frame,2017,Consumer Electronics,2215 +87,a0a6f9b3DbcBEb5,Mays-Preston,http://www.browning-key.com/,Mali,User-centric heuristic focus group,2006,Military Industry,5786 +88,8cC1bDa330a5871,Pineda-Morton,https://www.carr.com/,United States Virgin Islands,Grass-roots methodical info-mediaries,1991,Printing,6168 +89,ED889CB2FE9cbd3,Huang and Sons,https://www.bolton.com/,Eritrea,Re-contextualized dynamic hierarchy,1981,Semiconductors,7484 +90,F4Dc1417BC6cb8f,Gilbert-Simon,https://www.bradford.biz/,Burundi,Grass-roots radical parallelism,1973,Newspapers / Journalism,1927 +91,7ABc3c7ecA03B34,Sampson-Griffith,http://hendricks.org/,Benin,Multi-layered composite paradigm,1972,Textiles,3881 +92,4e0719FBE38e0aB,Miles-Dominguez,http://www.turner.com/,Gibraltar,Organized empowering forecast,1996,Civic / Social Organization,897 +93,dEbDAAeDfaed00A,Rowe and Sons,https://www.simpson.org/,El Salvador,Balanced multimedia knowledgebase,1978,Facilities Services,8172 +94,61BDeCfeFD0cEF5,"Valenzuela, Holmes and Rowland",https://www.dorsey.net/,Taiwan,Persistent tertiary focus group,1999,Transportation,1483 +95,4e91eD25f486110,"Best, Wade and Shepard",https://zimmerman.com/,Zimbabwe,Innovative background definition,1991,Gambling / Casinos,4873 +96,0a0bfFbBbB8eC7c,Holmes Group,https://mcdowell.org/,Ethiopia,Right-sized zero tolerance focus group,1975,Photography,2988 +97,BA6Cd9Dae2Efd62,Good Ltd,http://duffy.com/,Anguilla,Reverse-engineered composite moratorium,1971,Consumer Services,4292 +98,E7df80C60Abd7f9,Clements-Espinoza,http://www.flowers.net/,Falkland Islands (Malvinas),Progressive modular hub,1991,Broadcast Media,236 +99,AFc285dbE2fEd24,Mendez Inc,https://www.burke.net/,Kyrgyz Republic,User-friendly exuding migration,1993,Education Management,339 +100,e9eB5A60Cef8354,Watkins-Kaiser,http://www.herring.com/,Togo,Synergistic background access,2009,Financial Services,2785 diff --git a/docs/static/img/logo.svg b/docs/static/img/logo.svg index 136c4c835821060875515cb91d27b1f056e9b95f..3972c658bbf186d4864aa945bcabab384ac91f39 100644 GIT binary patch delta 15 Wcmdn>i*e^KMwZS1KlhESrnLY$uLeW_ delta 16 Xcmdn_i*eU4M%K;%KX=}ZET**pKF|hH diff --git a/src/frontend/set_proxy.sh b/src/frontend/set_proxy.sh old mode 100755 new mode 100644 From fe3a9c61df33991a393827208951a954aa1f9786 Mon Sep 17 00:00:00 2001 From: DiogenesBR Date: Thu, 24 Aug 2023 14:57:18 +0000 Subject: [PATCH 077/134] fix the imports of orjson_dumps --- src/backend/langflow/components/utilities/GetRequest.py | 2 +- .../langflow/components/utilities/JSONDocumentBuilder.py | 2 +- src/backend/langflow/components/utilities/PostRequest.py | 2 +- src/backend/langflow/components/utilities/UpdateRequest.py | 2 +- src/backend/langflow/interface/initialize/utils.py | 2 +- src/backend/langflow/services/cache/utils.py | 2 +- src/backend/langflow/template/frontend_node/llms.py | 2 +- src/backend/langflow/template/frontend_node/utilities.py | 2 +- tests/test_cache.py | 2 +- tests/test_database.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/backend/langflow/components/utilities/GetRequest.py b/src/backend/langflow/components/utilities/GetRequest.py index c8182ad0d..13ff0dc23 100644 --- a/src/backend/langflow/components/utilities/GetRequest.py +++ b/src/backend/langflow/components/utilities/GetRequest.py @@ -1,6 +1,6 @@ from langflow import CustomComponent from langchain.schema import Document -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps import requests from typing import Optional diff --git a/src/backend/langflow/components/utilities/JSONDocumentBuilder.py b/src/backend/langflow/components/utilities/JSONDocumentBuilder.py index e41b6b6af..26a2afd94 100644 --- a/src/backend/langflow/components/utilities/JSONDocumentBuilder.py +++ b/src/backend/langflow/components/utilities/JSONDocumentBuilder.py @@ -13,7 +13,7 @@ from langflow import CustomComponent from langchain.schema import Document -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps class JSONDocumentBuilder(CustomComponent): diff --git a/src/backend/langflow/components/utilities/PostRequest.py b/src/backend/langflow/components/utilities/PostRequest.py index 9d5ab504f..4f7100d43 100644 --- a/src/backend/langflow/components/utilities/PostRequest.py +++ b/src/backend/langflow/components/utilities/PostRequest.py @@ -1,6 +1,6 @@ from langflow import CustomComponent from langchain.schema import Document -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps import requests from typing import Optional diff --git a/src/backend/langflow/components/utilities/UpdateRequest.py b/src/backend/langflow/components/utilities/UpdateRequest.py index 569dac013..6e8991794 100644 --- a/src/backend/langflow/components/utilities/UpdateRequest.py +++ b/src/backend/langflow/components/utilities/UpdateRequest.py @@ -2,7 +2,7 @@ from typing import List, Optional import requests from langflow import CustomComponent from langchain.schema import Document -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps class UpdateRequest(CustomComponent): diff --git a/src/backend/langflow/interface/initialize/utils.py b/src/backend/langflow/interface/initialize/utils.py index 116673645..199626de5 100644 --- a/src/backend/langflow/interface/initialize/utils.py +++ b/src/backend/langflow/interface/initialize/utils.py @@ -1,6 +1,6 @@ import contextlib import json -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps import orjson from typing import Any, Dict, List diff --git a/src/backend/langflow/services/cache/utils.py b/src/backend/langflow/services/cache/utils.py index e89022167..a36243b75 100644 --- a/src/backend/langflow/services/cache/utils.py +++ b/src/backend/langflow/services/cache/utils.py @@ -8,7 +8,7 @@ from collections import OrderedDict from pathlib import Path from typing import Any, Dict from appdirs import user_cache_dir -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps CACHE: Dict[str, Any] = {} diff --git a/src/backend/langflow/template/frontend_node/llms.py b/src/backend/langflow/template/frontend_node/llms.py index fdf0a2b5b..01098724e 100644 --- a/src/backend/langflow/template/frontend_node/llms.py +++ b/src/backend/langflow/template/frontend_node/llms.py @@ -1,5 +1,5 @@ from typing import Optional -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode diff --git a/src/backend/langflow/template/frontend_node/utilities.py b/src/backend/langflow/template/frontend_node/utilities.py index fa0b55332..9dedacd0f 100644 --- a/src/backend/langflow/template/frontend_node/utilities.py +++ b/src/backend/langflow/template/frontend_node/utilities.py @@ -1,6 +1,6 @@ import ast from typing import Optional -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode diff --git a/tests/test_cache.py b/tests/test_cache.py index f3d0cabda..edf205a05 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -1,5 +1,5 @@ import json -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps import orjson from langflow.graph import Graph diff --git a/tests/test_database.py b/tests/test_database.py index d23d67a36..6976f963a 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,4 +1,4 @@ -from langflow.database.models.base import orjson_dumps +from langflow.services.database.models.base import orjson_dumps import orjson import pytest From c1c5b2242f9b13058136f4b951c4dec0bff26f7e Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 17:08:24 -0300 Subject: [PATCH 078/134] fix(buildTrigger/index.tsx): change the success message title from "Code is ready to run" to "Flow is ready to run" for better clarity and accuracy --- .../src/components/chatComponent/buildTrigger/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx index 9eb25bb37..66bc0d371 100644 --- a/src/frontend/src/components/chatComponent/buildTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/buildTrigger/index.tsx @@ -62,7 +62,7 @@ export default function BuildTrigger({ } if (errors.length === 0 && allNodesValid) { setSuccessData({ - title: "Code is ready to run", + title: "Flow is ready to run", }); } } catch (error) { From 4fab63e5de028784ead3263b049e093823f73766 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 17:10:02 -0300 Subject: [PATCH 079/134] code format --- .../components/codeTabsComponent/index.tsx | 48 +++++++++++++------ src/frontend/src/contexts/alertContext.tsx | 2 +- .../src/modals/codeAreaModal/index.tsx | 3 +- .../extraSidebarComponent/index.tsx | 3 +- .../src/types/utils/reactflowUtils.ts | 4 +- src/frontend/src/utils/reactflowUtils.ts | 5 +- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 2c61fed56..fcd7385d7 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -28,13 +28,13 @@ import { TabsList, TabsTrigger, } from "../../components/ui/tabs"; +import { alertContext } from "../../contexts/alertContext"; import { darkContext } from "../../contexts/darkContext"; +import { typesContext } from "../../contexts/typesContext"; import { codeTabsPropsType } from "../../types/components"; +import { unselectAllNodes } from "../../utils/reactflowUtils"; import { classNames } from "../../utils/utils"; import IconComponent from "../genericIconComponent"; -import { unselectAllNodes } from "../../utils/reactflowUtils"; -import { typesContext } from "../../contexts/typesContext"; -import { alertContext } from "../../contexts/alertContext"; export default function CodeTabsComponent({ flow, @@ -56,19 +56,19 @@ export default function CodeTabsComponent({ setData(flow["data"]!["nodes"]); } }, [flow]); - + useEffect(() => { unselectAllNodes({ data, updateNodes: (nodes) => { reactFlowInstance?.setNodes(nodes); - } + }, }); return () => { if (isTweakPage) setIsTweakPage(false); }; - }, []) + }, []); const copyToClipboard = () => { if (!navigator.clipboard || !navigator.clipboard.writeText) { @@ -295,7 +295,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -342,7 +344,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -385,7 +389,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -416,7 +422,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = e; return newInputList; @@ -505,7 +513,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -539,7 +549,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -589,7 +601,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -642,7 +656,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; @@ -695,7 +711,9 @@ export default function CodeTabsComponent({ setData((old) => { let newInputList = cloneDeep(old); - newInputList![i].data.node.template[ + newInputList![ + i + ].data.node.template[ templateField ].value = target; return newInputList; diff --git a/src/frontend/src/contexts/alertContext.tsx b/src/frontend/src/contexts/alertContext.tsx index 46a0d8a3b..98b2fdef8 100644 --- a/src/frontend/src/contexts/alertContext.tsx +++ b/src/frontend/src/contexts/alertContext.tsx @@ -27,7 +27,7 @@ const initialValue: alertContextType = { clearNotificationList: () => {}, removeFromNotificationList: () => {}, isTweakPage: false, - setIsTweakPage: () => {}, + setIsTweakPage: () => {}, }; export const alertContext = createContext (initialValue); diff --git a/src/frontend/src/modals/codeAreaModal/index.tsx b/src/frontend/src/modals/codeAreaModal/index.tsx index e4ef45f34..5c17e42a9 100644 --- a/src/frontend/src/modals/codeAreaModal/index.tsx +++ b/src/frontend/src/modals/codeAreaModal/index.tsx @@ -28,7 +28,8 @@ export default function CodeAreaModal({ const { dark } = useContext(darkContext); const { reactFlowInstance } = useContext(typesContext); const [height, setHeight] = useState (null); - const { setErrorData, setSuccessData, isTweakPage } = useContext(alertContext); + const { setErrorData, setSuccessData, isTweakPage } = + useContext(alertContext); const [error, setError] = useState<{ detail: { error: string | undefined; traceback: string | undefined }; } | null>(null); diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index f1a84bce9..12cd543c6 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -21,7 +21,8 @@ export default function ExtraSidebar(): JSX.Element { const { data, templates } = useContext(typesContext); const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } = useContext(TabsContext); - const { setSuccessData, setErrorData, setIsTweakPage } = useContext(alertContext); + const { setSuccessData, setErrorData, setIsTweakPage } = + useContext(alertContext); const [dataFilter, setFilterData] = useState(data); const [search, setSearch] = useState(""); const isPending = tabsState[tabId]?.isPending; diff --git a/src/frontend/src/types/utils/reactflowUtils.ts b/src/frontend/src/types/utils/reactflowUtils.ts index 6ec40512d..9944fdba4 100644 --- a/src/frontend/src/types/utils/reactflowUtils.ts +++ b/src/frontend/src/types/utils/reactflowUtils.ts @@ -10,6 +10,6 @@ export type cleanEdgesType = { }; export type unselectAllNodesType = { - updateNodes: (nodes: Node[]) => void, - data: Node[] | null + updateNodes: (nodes: Node[]) => void; + data: Node[] | null; }; diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 6a12f3edd..87971ef9b 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -9,7 +9,10 @@ import { import { specialCharsRegex } from "../constants/constants"; import { APITemplateType } from "../types/api"; import { FlowType, NodeType } from "../types/flow"; -import { cleanEdgesType, unselectAllNodesType } from "../types/utils/reactflowUtils"; +import { + cleanEdgesType, + unselectAllNodesType, +} from "../types/utils/reactflowUtils"; import { toNormalCase } from "./utils"; export function cleanEdges({ From c49ee222be51cc214e706015d10c4a75ffdec1f2 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 17:11:54 -0300 Subject: [PATCH 080/134] update git attributes --- .gitattributes | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 616cbc4c5..824297736 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,7 +12,6 @@ *.tsx text *.md text *.mdx text -*.svg text *.yml text *.yaml text *.xml text @@ -30,4 +29,5 @@ Dockerfile text *.jpg binary *.ico binary *.gif binary -*.mp4 binary \ No newline at end of file +*.mp4 binary +*.svg binary \ No newline at end of file From e863f797821482d53921cdf19e40458e14bf53a1 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 17:15:05 -0300 Subject: [PATCH 081/134] update git attributes --- .gitattributes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 824297736..5d66e9c68 100644 --- a/.gitattributes +++ b/.gitattributes @@ -30,4 +30,5 @@ Dockerfile text *.ico binary *.gif binary *.mp4 binary -*.svg binary \ No newline at end of file +*.svg binary +*.csv binary \ No newline at end of file From 1a51cc0848211b8133da1994aaff90b740a40f9f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 24 Aug 2023 17:32:26 -0300 Subject: [PATCH 082/134] =?UTF-8?q?=F0=9F=93=A6=20feat(api=5Fkey):=20add?= =?UTF-8?q?=20ApiKey=20model=20and=20related=20classes=20for=20database=20?= =?UTF-8?q?operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📦 feat(component): add Component model and related classes for database operations 📦 feat(flow): add Flow model and related classes for database operations 📦 feat(token): add Token model for authentication 📦 feat(user): add User model and related classes for database operations 📦 feat(user): add utility functions for user operations --- .../database/models/api_key/__init__.py | 3 ++ .../database/models/{ => api_key}/api_key.py | 0 .../database/models/component/__init__.py | 3 ++ .../models/{ => component}/component.py | 0 .../services/database/models/flow/__init__.py | 3 ++ .../database/models/{ => flow}/flow.py | 3 -- .../database/models/token/__init__.py | 5 ++ .../database/models/{ => token}/token.py | 0 .../services/database/models/user/__init__.py | 8 +++ .../services/database/models/user/user.py | 40 ++++++++++++++ .../models/{user.py => user/utils.py} | 53 ++----------------- 11 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 src/backend/langflow/services/database/models/api_key/__init__.py rename src/backend/langflow/services/database/models/{ => api_key}/api_key.py (100%) create mode 100644 src/backend/langflow/services/database/models/component/__init__.py rename src/backend/langflow/services/database/models/{ => component}/component.py (100%) create mode 100644 src/backend/langflow/services/database/models/flow/__init__.py rename src/backend/langflow/services/database/models/{ => flow}/flow.py (94%) create mode 100644 src/backend/langflow/services/database/models/token/__init__.py rename src/backend/langflow/services/database/models/{ => token}/token.py (100%) create mode 100644 src/backend/langflow/services/database/models/user/__init__.py create mode 100644 src/backend/langflow/services/database/models/user/user.py rename src/backend/langflow/services/database/models/{user.py => user/utils.py} (51%) diff --git a/src/backend/langflow/services/database/models/api_key/__init__.py b/src/backend/langflow/services/database/models/api_key/__init__.py new file mode 100644 index 000000000..c97425ee8 --- /dev/null +++ b/src/backend/langflow/services/database/models/api_key/__init__.py @@ -0,0 +1,3 @@ +from .api_key import ApiKey, ApiKeyCreate, ApiKeyRead + +__all__ = ["ApiKey", "ApiKeyCreate", "ApiKeyRead"] diff --git a/src/backend/langflow/services/database/models/api_key.py b/src/backend/langflow/services/database/models/api_key/api_key.py similarity index 100% rename from src/backend/langflow/services/database/models/api_key.py rename to src/backend/langflow/services/database/models/api_key/api_key.py diff --git a/src/backend/langflow/services/database/models/component/__init__.py b/src/backend/langflow/services/database/models/component/__init__.py new file mode 100644 index 000000000..c787c3e04 --- /dev/null +++ b/src/backend/langflow/services/database/models/component/__init__.py @@ -0,0 +1,3 @@ +from .component import Component, ComponentModel + +__all__ = ["Component", "ComponentModel"] diff --git a/src/backend/langflow/services/database/models/component.py b/src/backend/langflow/services/database/models/component/component.py similarity index 100% rename from src/backend/langflow/services/database/models/component.py rename to src/backend/langflow/services/database/models/component/component.py diff --git a/src/backend/langflow/services/database/models/flow/__init__.py b/src/backend/langflow/services/database/models/flow/__init__.py new file mode 100644 index 000000000..7c7cc0172 --- /dev/null +++ b/src/backend/langflow/services/database/models/flow/__init__.py @@ -0,0 +1,3 @@ +from .flow import Flow, FlowCreate, FlowRead, FlowUpdate + +__all__ = ["Flow", "FlowCreate", "FlowRead", "FlowUpdate"] diff --git a/src/backend/langflow/services/database/models/flow.py b/src/backend/langflow/services/database/models/flow/flow.py similarity index 94% rename from src/backend/langflow/services/database/models/flow.py rename to src/backend/langflow/services/database/models/flow/flow.py index 2bc83f9dc..a05de5791 100644 --- a/src/backend/langflow/services/database/models/flow.py +++ b/src/backend/langflow/services/database/models/flow/flow.py @@ -6,8 +6,6 @@ from sqlmodel import Field, JSON, Column from uuid import UUID, uuid4 from typing import Dict, Optional -# if TYPE_CHECKING: - class FlowBase(SQLModelSerializable): name: str = Field(index=True) @@ -16,7 +14,6 @@ class FlowBase(SQLModelSerializable): @validator("data") def validate_json(v): - # dict_keys(['description', 'name', 'id', 'data']) if not v: return v if not isinstance(v, dict): diff --git a/src/backend/langflow/services/database/models/token/__init__.py b/src/backend/langflow/services/database/models/token/__init__.py new file mode 100644 index 000000000..9b9fa397d --- /dev/null +++ b/src/backend/langflow/services/database/models/token/__init__.py @@ -0,0 +1,5 @@ +from .token import Token + +__all__ = [ + "Token", +] diff --git a/src/backend/langflow/services/database/models/token.py b/src/backend/langflow/services/database/models/token/token.py similarity index 100% rename from src/backend/langflow/services/database/models/token.py rename to src/backend/langflow/services/database/models/token/token.py diff --git a/src/backend/langflow/services/database/models/user/__init__.py b/src/backend/langflow/services/database/models/user/__init__.py new file mode 100644 index 000000000..da9170eb7 --- /dev/null +++ b/src/backend/langflow/services/database/models/user/__init__.py @@ -0,0 +1,8 @@ +from .user import User, UserCreate, UserRead, UserUpdate + +__all__ = [ + "User", + "UserCreate", + "UserRead", + "UserUpdate", +] diff --git a/src/backend/langflow/services/database/models/user/user.py b/src/backend/langflow/services/database/models/user/user.py new file mode 100644 index 000000000..3a4308b42 --- /dev/null +++ b/src/backend/langflow/services/database/models/user/user.py @@ -0,0 +1,40 @@ +from langflow.services.database.models.base import SQLModel, SQLModelSerializable +from sqlmodel import Field + + +from datetime import datetime +from typing import Optional +from uuid import UUID, uuid4 + + +class User(SQLModelSerializable, table=True): + id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) + username: str = Field(index=True, unique=True) + password: str = Field() + is_active: bool = Field(default=False) + is_superuser: bool = Field(default=False) + create_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) + last_login_at: Optional[datetime] = Field() + + +class UserCreate(SQLModel): + username: str = Field() + password: str = Field() + + +class UserRead(SQLModel): + id: UUID = Field(default_factory=uuid4) + username: str = Field() + is_active: bool = Field() + is_superuser: bool = Field() + create_at: datetime = Field() + updated_at: datetime = Field() + last_login_at: Optional[datetime] = Field() + + +class UserUpdate(SQLModel): + username: Optional[str] = Field() + is_active: Optional[bool] = Field() + is_superuser: Optional[bool] = Field() + last_login_at: Optional[datetime] = Field() diff --git a/src/backend/langflow/services/database/models/user.py b/src/backend/langflow/services/database/models/user/utils.py similarity index 51% rename from src/backend/langflow/services/database/models/user.py rename to src/backend/langflow/services/database/models/user/utils.py index f9a3c80f8..3b600bd9a 100644 --- a/src/backend/langflow/services/database/models/user.py +++ b/src/backend/langflow/services/database/models/user/utils.py @@ -1,53 +1,10 @@ +from datetime import datetime, timezone +from uuid import UUID from fastapi import Depends, HTTPException -from langflow.services.database.models.base import SQLModel, SQLModelSerializable +from langflow.services.database.models.user.user import User, UserUpdate from langflow.services.utils import get_session -from pydantic import BaseModel from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session -from sqlmodel import Field - - -from datetime import datetime, timezone -from typing import List, Optional -from uuid import UUID, uuid4 - - -class User(SQLModelSerializable, table=True): - id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) - username: str = Field(index=True, unique=True) - password: str = Field() - is_active: bool = Field(default=False) - is_superuser: bool = Field(default=False) - create_at: datetime = Field(default_factory=datetime.utcnow) - updated_at: datetime = Field(default_factory=datetime.utcnow) - last_login_at: Optional[datetime] = Field() - - -class UserAddModel(SQLModel): - username: str = Field() - password: str = Field() - - -class UserListModel(SQLModel): - id: UUID = Field(default_factory=uuid4) - username: str = Field() - is_active: bool = Field() - is_superuser: bool = Field() - create_at: datetime = Field() - updated_at: datetime = Field() - last_login_at: Optional[datetime] = Field() - - -class UserPatchModel(SQLModel): - username: Optional[str] = Field() - is_active: Optional[bool] = Field() - is_superuser: Optional[bool] = Field() - last_login_at: Optional[datetime] = Field() - - -class UsersResponse(BaseModel): - total_count: int - users: List[UserListModel] def get_user_by_username(db: Session, username: str) -> User: @@ -61,7 +18,7 @@ def get_user_by_id(db: Session, id: UUID) -> User: def update_user( - user_id: UUID, user: UserPatchModel, db: Session = Depends(get_session) + user_id: UUID, user: UserUpdate, db: Session = Depends(get_session) ) -> User: user_db = get_user_by_username(db, user.username) # type: ignore if user_db and user_db.id != user_id: @@ -90,6 +47,6 @@ def update_user( def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)): - user_data = UserPatchModel(last_login_at=datetime.now(timezone.utc)) # type: ignore + user_data = UserUpdate(last_login_at=datetime.now(timezone.utc)) # type: ignore return update_user(user_id, user_data, db) From d9cbf17b1a779f5a05cbf29798edea56e4823aa1 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 24 Aug 2023 17:41:41 -0300 Subject: [PATCH 083/134] =?UTF-8?q?=F0=9F=94=84=20chore(router.py):=20add?= =?UTF-8?q?=20users=5Frouter=20and=20api=5Fkey=5Frouter=20to=20the=20APIRo?= =?UTF-8?q?uter=20to=20include=20the=20new=20routes=20for=20users=20and=20?= =?UTF-8?q?api=20keys=20=F0=9F=94=84=20chore(=5F=5Finit=5F=5F.py):=20add?= =?UTF-8?q?=20users=5Frouter=20and=20api=5Fkey=5Frouter=20to=20the=20=5F?= =?UTF-8?q?=5Fall=5F=5F=20list=20to=20include=20the=20new=20routes=20for?= =?UTF-8?q?=20users=20and=20api=20keys=20=F0=9F=86=95=20feat(api=5Fkey.py)?= =?UTF-8?q?:=20add=20new=20routes=20for=20retrieving,=20creating,=20and=20?= =?UTF-8?q?deleting=20API=20keys=20=F0=9F=86=95=20feat(login.py):=20add=20?= =?UTF-8?q?new=20routes=20for=20user=20login,=20auto=20login,=20and=20toke?= =?UTF-8?q?n=20refresh=20=F0=9F=86=95=20feat(schemas.py):=20add=20new=20sc?= =?UTF-8?q?hemas=20for=20API=20key=20response=20and=20users=20response=20?= =?UTF-8?q?=F0=9F=86=95=20feat(users.py):=20add=20new=20routes=20for=20add?= =?UTF-8?q?ing,=20reading,=20updating,=20and=20deleting=20users=20?= =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20chore(health.py):=20remove=20health=20r?= =?UTF-8?q?outer=20as=20it=20is=20no=20longer=20needed=20=F0=9F=94=84=20ch?= =?UTF-8?q?ore(utils.py):=20update=20import=20statements=20for=20User=20mo?= =?UTF-8?q?del=20and=20update=5Fuser=5Flast=5Flogin=5Fat=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/router.py | 4 ++++ src/backend/langflow/api/v1/__init__.py | 4 ++++ .../langflow/{routers => api/v1}/api_key.py | 0 .../langflow/{routers => api/v1}/login.py | 0 src/backend/langflow/api/v1/schemas.py | 6 +++++ .../langflow/{routers => api/v1}/users.py | 22 +++++++++---------- src/backend/langflow/routers/health.py | 8 ------- src/backend/langflow/services/auth/utils.py | 4 ++-- 8 files changed, 27 insertions(+), 21 deletions(-) rename src/backend/langflow/{routers => api/v1}/api_key.py (100%) rename src/backend/langflow/{routers => api/v1}/login.py (100%) rename src/backend/langflow/{routers => api/v1}/users.py (88%) delete mode 100644 src/backend/langflow/routers/health.py diff --git a/src/backend/langflow/api/router.py b/src/backend/langflow/api/router.py index ea1938a75..70cce437d 100644 --- a/src/backend/langflow/api/router.py +++ b/src/backend/langflow/api/router.py @@ -6,6 +6,8 @@ from langflow.api.v1 import ( validate_router, flows_router, component_router, + users_router, + api_key_router, ) router = APIRouter( @@ -16,3 +18,5 @@ router.include_router(endpoints_router) router.include_router(validate_router) router.include_router(component_router) router.include_router(flows_router) +router.include_router(users_router) +router.include_router(api_key_router) diff --git a/src/backend/langflow/api/v1/__init__.py b/src/backend/langflow/api/v1/__init__.py index b6e7b36d8..38f1c9148 100644 --- a/src/backend/langflow/api/v1/__init__.py +++ b/src/backend/langflow/api/v1/__init__.py @@ -3,6 +3,8 @@ from langflow.api.v1.validate import router as validate_router from langflow.api.v1.chat import router as chat_router from langflow.api.v1.flows import router as flows_router from langflow.api.v1.components import router as component_router +from langflow.api.v1.users import router as users_router +from langflow.api.v1.api_key import router as api_key_router __all__ = [ "chat_router", @@ -10,4 +12,6 @@ __all__ = [ "component_router", "validate_router", "flows_router", + "users_router", + "api_key_router", ] diff --git a/src/backend/langflow/routers/api_key.py b/src/backend/langflow/api/v1/api_key.py similarity index 100% rename from src/backend/langflow/routers/api_key.py rename to src/backend/langflow/api/v1/api_key.py diff --git a/src/backend/langflow/routers/login.py b/src/backend/langflow/api/v1/login.py similarity index 100% rename from src/backend/langflow/routers/login.py rename to src/backend/langflow/api/v1/login.py diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index d188e5c7a..d788469fa 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union from langflow.services.database.models.api_key import ApiKeyRead from langflow.services.database.models.flow import FlowCreate, FlowRead +from langflow.services.database.models.user import UserRead from pydantic import BaseModel, Field, validator import json @@ -141,3 +142,8 @@ class ApiKeyResponse(BaseModel): total_count: int user_id: str api_keys: List[ApiKeyRead] + + +class UsersResponse(BaseModel): + total_count: int + users: List[UserRead] diff --git a/src/backend/langflow/routers/users.py b/src/backend/langflow/api/v1/users.py similarity index 88% rename from src/backend/langflow/routers/users.py rename to src/backend/langflow/api/v1/users.py index 2a944cb64..33ddc9763 100644 --- a/src/backend/langflow/routers/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -1,10 +1,10 @@ from uuid import UUID +from langflow.api.v1.schemas import UsersResponse from langflow.services.database.models.user import ( User, - UserAddModel, - UserListModel, - UserPatchModel, - UsersResponse, + UserCreate, + UserRead, + UserUpdate, ) from sqlalchemy import func @@ -15,16 +15,16 @@ from fastapi import APIRouter, Depends, HTTPException from langflow.services.utils import get_session from langflow.services.auth.utils import get_current_active_user, get_password_hash -from langflow.services.database.models.user import ( +from langflow.services.database.models.user.utils import ( update_user, ) router = APIRouter(tags=["Login"]) -@router.post("/user", response_model=UserListModel) +@router.post("/user", response_model=UserRead) def add_user( - user: UserAddModel, + user: UserCreate, db: Session = Depends(get_session), ) -> User: """ @@ -44,7 +44,7 @@ def add_user( return new_user -@router.get("/user", response_model=UserListModel) +@router.get("/user", response_model=UserRead) def read_current_user(current_user: User = Depends(get_current_active_user)) -> User: """ Retrieve the current user's data. @@ -70,14 +70,14 @@ def read_all_users( return UsersResponse( total_count=total_count, # type: ignore - users=[UserListModel(**dict(user.User)) for user in users], + users=[UserRead(**dict(user.User)) for user in users], ) -@router.patch("/user/{user_id}", response_model=UserListModel) +@router.patch("/user/{user_id}", response_model=UserRead) def patch_user( user_id: UUID, - user: UserPatchModel, + user: UserUpdate, _: Session = Depends(get_current_active_user), db: Session = Depends(get_session), ) -> User: diff --git a/src/backend/langflow/routers/health.py b/src/backend/langflow/routers/health.py deleted file mode 100644 index 244ef001d..000000000 --- a/src/backend/langflow/routers/health.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.get("/health") -def get_health(): - return {"status": "OK"} diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index f897151e7..aa4bec669 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -4,8 +4,8 @@ from jose import JWTError, jwt from typing import Annotated from uuid import UUID from langflow.services.auth.service import AuthManager -from langflow.services.database.models.user import ( - User, +from langflow.services.database.models.user.user import User +from langflow.services.database.models.user.utils import ( get_user_by_id, get_user_by_username, update_user_last_login_at, From 58121cc6ca7b53a69ff2e45cfd45913a1ca955a5 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 24 Aug 2023 17:42:33 -0300 Subject: [PATCH 084/134] =?UTF-8?q?=F0=9F=94=A5=20refactor(main.py):=20rem?= =?UTF-8?q?ove=20unused=20routers=20from=20the=20app=20to=20improve=20code?= =?UTF-8?q?=20cleanliness=20and=20reduce=20unnecessary=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/main.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 7045ec99d..a383a2afa 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -6,7 +6,7 @@ from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from langflow.api import router -from langflow.routers import api_key, login, users, health + from langflow.interface.utils import setup_llm_caching from langflow.services.database.utils import initialize_database @@ -31,11 +31,6 @@ def create_app(): allow_headers=["*"], ) - app.include_router(login.router) - app.include_router(api_key.router) - app.include_router(users.router) - app.include_router(health.router) - app.include_router(router) app.on_event("startup")(initialize_services) From 41ef2fd2f72cc82d5bd4314704b7cf60b84a1311 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Thu, 24 Aug 2023 17:42:53 -0300 Subject: [PATCH 085/134] =?UTF-8?q?=F0=9F=94=A7=20fix(alembic):=20fix=20in?= =?UTF-8?q?dentation=20and=20formatting=20issues=20in=20add=5Fapikey=5Ftab?= =?UTF-8?q?le=20migration=20script=20=E2=9C=A8=20feat(alembic):=20add=20su?= =?UTF-8?q?pport=20for=20creating=20apikey=20and=20user=20tables=20in=20th?= =?UTF-8?q?e=20database=20=F0=9F=94=A5=20chore(alembic):=20remove=20flowst?= =?UTF-8?q?yle=20and=20component=20tables=20from=20the=20database=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../versions/5512e39b4012_add_apikey_table.py | 126 ++++++++++-------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py b/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py index 8c8ae75fd..02db82e71 100644 --- a/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py +++ b/src/backend/langflow/alembic/versions/5512e39b4012_add_apikey_table.py @@ -5,6 +5,8 @@ Revises: 0a534bdfd84b Create Date: 2023-08-23 21:05:51.042203 """ + +import contextlib from typing import Sequence, Union from alembic import op @@ -13,72 +15,86 @@ import sqlmodel # revision identifiers, used by Alembic. -revision: str = '5512e39b4012' -down_revision: Union[str, None] = '0a534bdfd84b' +revision: str = "5512e39b4012" +down_revision: Union[str, None] = "0a534bdfd84b" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('apikey', - sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('create_at', sa.DateTime(), nullable=False), - sa.Column('last_used_at', sa.DateTime(), nullable=True), - sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('id') - ) - op.create_index(op.f('ix_apikey_api_key'), 'apikey', ['api_key'], unique=True) - op.create_table('user', - sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False), - sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('password', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('is_superuser', sa.Boolean(), nullable=False), - sa.Column('create_at', sa.DateTime(), nullable=False), - sa.Column('updated_at', sa.DateTime(), nullable=False), - sa.Column('last_login_at', sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('id') - ) - op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True) - op.drop_table('flowstyle') - op.drop_index('ix_component_frontend_node_id', table_name='component') - op.drop_index('ix_component_name', table_name='component') - op.drop_table('component') + with contextlib.suppress(sa.exc.OperationalError): + op.create_table( + "apikey", + sa.Column("api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("create_at", sa.DateTime(), nullable=False), + sa.Column("last_used_at", sa.DateTime(), nullable=True), + sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("id"), + ) + op.create_index(op.f("ix_apikey_api_key"), "apikey", ["api_key"], unique=True) + + with contextlib.suppress(sa.exc.OperationalError): + op.create_table( + "user", + sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column("username", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("password", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("is_superuser", sa.Boolean(), nullable=False), + sa.Column("create_at", sa.DateTime(), nullable=False), + sa.Column("updated_at", sa.DateTime(), nullable=False), + sa.Column("last_login_at", sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("id"), + ) + op.create_index(op.f("ix_user_username"), "user", ["username"], unique=True) + with contextlib.suppress(sa.exc.OperationalError): + op.drop_table("flowstyle") + with contextlib.suppress(sa.exc.OperationalError): + op.drop_index("ix_component_frontend_node_id", table_name="component") + op.drop_index("ix_component_name", table_name="component") + op.drop_table("component") # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table('component', - sa.Column('id', sa.CHAR(length=32), nullable=False), - sa.Column('frontend_node_id', sa.CHAR(length=32), nullable=False), - sa.Column('name', sa.VARCHAR(), nullable=False), - sa.Column('description', sa.VARCHAR(), nullable=True), - sa.Column('python_code', sa.VARCHAR(), nullable=True), - sa.Column('return_type', sa.VARCHAR(), nullable=True), - sa.Column('is_disabled', sa.BOOLEAN(), nullable=False), - sa.Column('is_read_only', sa.BOOLEAN(), nullable=False), - sa.Column('create_at', sa.DATETIME(), nullable=False), - sa.Column('update_at', sa.DATETIME(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "component", + sa.Column("id", sa.CHAR(length=32), nullable=False), + sa.Column("frontend_node_id", sa.CHAR(length=32), nullable=False), + sa.Column("name", sa.VARCHAR(), nullable=False), + sa.Column("description", sa.VARCHAR(), nullable=True), + sa.Column("python_code", sa.VARCHAR(), nullable=True), + sa.Column("return_type", sa.VARCHAR(), nullable=True), + sa.Column("is_disabled", sa.BOOLEAN(), nullable=False), + sa.Column("is_read_only", sa.BOOLEAN(), nullable=False), + sa.Column("create_at", sa.DATETIME(), nullable=False), + sa.Column("update_at", sa.DATETIME(), nullable=False), + sa.PrimaryKeyConstraint("id"), ) - op.create_index('ix_component_name', 'component', ['name'], unique=False) - op.create_index('ix_component_frontend_node_id', 'component', ['frontend_node_id'], unique=False) - op.create_table('flowstyle', - sa.Column('color', sa.VARCHAR(), nullable=False), - sa.Column('emoji', sa.VARCHAR(), nullable=False), - sa.Column('flow_id', sa.CHAR(length=32), nullable=True), - sa.Column('id', sa.CHAR(length=32), nullable=False), - sa.ForeignKeyConstraint(['flow_id'], ['flow.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('id') + op.create_index("ix_component_name", "component", ["name"], unique=False) + op.create_index( + "ix_component_frontend_node_id", "component", ["frontend_node_id"], unique=False ) - op.drop_index(op.f('ix_user_username'), table_name='user') - op.drop_table('user') - op.drop_index(op.f('ix_apikey_api_key'), table_name='apikey') - op.drop_table('apikey') + op.create_table( + "flowstyle", + sa.Column("color", sa.VARCHAR(), nullable=False), + sa.Column("emoji", sa.VARCHAR(), nullable=False), + sa.Column("flow_id", sa.CHAR(length=32), nullable=True), + sa.Column("id", sa.CHAR(length=32), nullable=False), + sa.ForeignKeyConstraint( + ["flow_id"], + ["flow.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("id"), + ) + op.drop_index(op.f("ix_user_username"), table_name="user") + op.drop_table("user") + op.drop_index(op.f("ix_apikey_api_key"), table_name="apikey") + op.drop_table("apikey") # ### end Alembic commands ### From bf568ad22f906fe0a3a46bc381a44ab5c7179fbb Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 19:41:23 -0300 Subject: [PATCH 086/134] update attribute --- .gitattributes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 5d66e9c68..4b878819c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -31,4 +31,4 @@ Dockerfile text *.gif binary *.mp4 binary *.svg binary -*.csv binary \ No newline at end of file +*.csv binary From cea8acacd8dbec94a90627b53846694864d5d89e Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 19:42:48 -0300 Subject: [PATCH 087/134] update type to avoid undefined --- src/frontend/src/modals/flowSettingsModal/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index ed91f5dbb..7729f78fe 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -15,11 +15,11 @@ export default function FlowSettingsModal({ const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext); const flow = flows.find((f) => f.id === tabId); useEffect(() => { - setName(flow.name); - setDescription(flow.description); - }, [flow.name, flow.description]); - const [name, setName] = useState(flow.name); - const [description, setDescription] = useState(flow.description); + setName(flow!.name); + setDescription(flow!.description); + }, [flow!.name, flow!.description]); + const [name, setName] = useState(flow!.name); + const [description, setDescription] = useState(flow!.description); const [invalidName, setInvalidName] = useState(false); function handleClick(): void { From 9b3e4f27a54d4c2242f8f542a9eff3bba39ed9d1 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 24 Aug 2023 19:50:17 -0300 Subject: [PATCH 088/134] fix(tabsContext.tsx): handle error message properly when saving changes in the tabs provider The error handling in the tabs provider has been improved to display a more meaningful error message when there is an error while saving changes. Instead of directly assigning the error to the `setErrorData` state variable, the error message is now wrapped in an object with a title and a list of error messages. This change ensures that the error message is displayed consistently and provides better context to the user. --- src/frontend/src/contexts/tabsContext.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 5c48e6e5b..2e3334f5e 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -30,6 +30,7 @@ import { import { getRandomDescription, getRandomName } from "../utils/utils"; import { alertContext } from "./alertContext"; import { typesContext } from "./typesContext"; +import { AxiosError } from "axios"; const uid = new ShortUniqueId({ length: 5 }); @@ -602,7 +603,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { }); } } catch (err) { - setErrorData(err as errorsVarType); + setErrorData({title: "Error while saving changes",list:[(err as AxiosError).message]}); } } From 6b82b730cd3d90eda40a773bf3f5d0efc524b47e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:09:37 -0300 Subject: [PATCH 089/134] =?UTF-8?q?=F0=9F=94=A5=20refactor(auth):=20remove?= =?UTF-8?q?=20unused=20code=20and=20dependencies=20in=20auth=20module=20?= =?UTF-8?q?=F0=9F=94=A5=20refactor(routers):=20remove=20unused=20code=20an?= =?UTF-8?q?d=20dependencies=20in=20routers=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/__init__.py | 0 src/backend/langflow/auth/auth.py | 219 ----------------------- src/backend/langflow/routers/__init__.py | 0 3 files changed, 219 deletions(-) delete mode 100644 src/backend/langflow/auth/__init__.py delete mode 100644 src/backend/langflow/auth/auth.py delete mode 100644 src/backend/langflow/routers/__init__.py diff --git a/src/backend/langflow/auth/__init__.py b/src/backend/langflow/auth/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py deleted file mode 100644 index f274a9523..000000000 --- a/src/backend/langflow/auth/auth.py +++ /dev/null @@ -1,219 +0,0 @@ -from uuid import UUID -from typing import Annotated -from jose import JWTError, jwt -from langflow.services.database.models.user import ( - User, - get_user_by_id, - get_user_by_username, -) -from sqlalchemy.orm import Session -from passlib.context import CryptContext -from fastapi.security import OAuth2PasswordBearer -from fastapi import Depends, HTTPException, status -from datetime import datetime, timedelta, timezone - -from langflow.services.utils import get_settings_manager, get_session - -from langflow.services.database.models.user import ( - update_user_last_login_at, -) - - -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") - - -async def get_current_user( - token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_session) -) -> User: - settings_manager = get_settings_manager() - - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - - try: - payload = jwt.decode( - token, - settings_manager.auth_settings.SECRET_KEY, - algorithms=[settings_manager.auth_settings.ALGORITHM], - ) - user_id: UUID = payload.get("sub") # type: ignore - token_type: str = payload.get("type") # type: ignore - - if user_id is None or token_type: - raise credentials_exception - except JWTError as e: - raise credentials_exception from e - - user = get_user_by_id(db, user_id) # type: ignore - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)] -): - if not current_user.is_active: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -def verify_password(plain_password, hashed_password): - return pwd_context.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return pwd_context.hash(password) - - -def create_token(data: dict, expires_delta: timedelta): - settings_manager = get_settings_manager() - - to_encode = data.copy() - expire = datetime.now(timezone.utc) + expires_delta - to_encode["exp"] = expire - - return jwt.encode( - to_encode, - settings_manager.auth_settings.SECRET_KEY, - algorithm=settings_manager.auth_settings.ALGORITHM, - ) - - -def create_super_user(db: Session = Depends(get_session)) -> User: - settings_manager = get_settings_manager() - - super_user = get_user_by_username( - db, settings_manager.auth_settings.FIRST_SUPERUSER - ) - - if not super_user: - super_user = User( - username=settings_manager.auth_settings.FIRST_SUPERUSER, - password=get_password_hash( - settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD - ), - is_superuser=True, - is_active=True, - last_login_at=None, - ) - - db.add(super_user) - db.commit() - db.refresh(super_user) - - return super_user - - -def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: - super_user = create_super_user(db) - - access_token_expires_longterm = timedelta(days=365) - access_token = create_token( - data={"sub": str(super_user.id)}, - expires_delta=access_token_expires_longterm, - ) - - # Update: last_login_at - update_user_last_login_at(super_user.id, db) - - return { - "access_token": access_token, - "refresh_token": None, - "token_type": "bearer", - } - - -def create_user_api_key(user_id: UUID) -> dict: - access_token = create_token( - data={"sub": str(user_id), "role": "api_key"}, - expires_delta=timedelta(days=365 * 2), - ) - - return {"api_key": access_token} - - -def get_user_id_from_token(token: str) -> UUID: - try: - user_id = jwt.get_unverified_claims(token)["sub"] - return UUID(user_id) - except (KeyError, JWTError, ValueError): - return UUID(int=0) - - -def create_user_tokens( - user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False -) -> dict: - settings_manager = get_settings_manager() - - access_token_expires = timedelta( - minutes=settings_manager.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES - ) - access_token = create_token( - data={"sub": str(user_id)}, - expires_delta=access_token_expires, - ) - - refresh_token_expires = timedelta( - minutes=settings_manager.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES - ) - refresh_token = create_token( - data={"sub": str(user_id), "type": "rf"}, - expires_delta=refresh_token_expires, - ) - - # Update: last_login_at - if update_last_login: - update_user_last_login_at(user_id, db) - - return { - "access_token": access_token, - "refresh_token": refresh_token, - "token_type": "bearer", - } - - -def create_refresh_token(refresh_token: str, db: Session = Depends(get_session)): - settings_manager = get_settings_manager() - - try: - payload = jwt.decode( - refresh_token, - settings_manager.auth_settings.SECRET_KEY, - algorithms=[settings_manager.auth_settings.ALGORITHM], - ) - user_id: UUID = payload.get("sub") # type: ignore - token_type: str = payload.get("type") # type: ignore - - if user_id is None or token_type is None: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token" - ) - - return create_user_tokens(user_id, db) - - except JWTError as e: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Invalid refresh token", - ) from e - - -def authenticate_user( - username: str, password: str, db: Session = Depends(get_session) -) -> User | None: - user = get_user_by_username(db, username) - - if not user: - return None - - if not user.is_active: - if not user.last_login_at: - raise HTTPException(status_code=400, detail="Waiting for approval") - raise HTTPException(status_code=400, detail="Inactive user") - - return user if verify_password(password, user.password) else None diff --git a/src/backend/langflow/routers/__init__.py b/src/backend/langflow/routers/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 43b2d62661204f385248f8faeaf0eb85a453f7f7 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:10:19 -0300 Subject: [PATCH 090/134] =?UTF-8?q?=F0=9F=94=80=20chore(router.py):=20add?= =?UTF-8?q?=20login=5Frouter=20to=20the=20APIRouter=20to=20include=20login?= =?UTF-8?q?=20functionality=20=F0=9F=94=80=20chore(=5F=5Finit=5F=5F.py):?= =?UTF-8?q?=20import=20and=20include=20login=5Frouter=20in=20the=20APIRout?= =?UTF-8?q?er=20to=20enable=20login=20functionality=20=F0=9F=94=80=20chore?= =?UTF-8?q?(login.py):=20add=20tags=20to=20the=20login=20router=20to=20cat?= =?UTF-8?q?egorize=20it=20as=20"Login"=20in=20the=20API=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/router.py | 2 ++ src/backend/langflow/api/v1/__init__.py | 2 ++ src/backend/langflow/api/v1/login.py | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/router.py b/src/backend/langflow/api/router.py index 70cce437d..dbaf20e75 100644 --- a/src/backend/langflow/api/router.py +++ b/src/backend/langflow/api/router.py @@ -8,6 +8,7 @@ from langflow.api.v1 import ( component_router, users_router, api_key_router, + login_router, ) router = APIRouter( @@ -20,3 +21,4 @@ router.include_router(component_router) router.include_router(flows_router) router.include_router(users_router) router.include_router(api_key_router) +router.include_router(login_router) diff --git a/src/backend/langflow/api/v1/__init__.py b/src/backend/langflow/api/v1/__init__.py index 38f1c9148..9335a4607 100644 --- a/src/backend/langflow/api/v1/__init__.py +++ b/src/backend/langflow/api/v1/__init__.py @@ -5,6 +5,7 @@ from langflow.api.v1.flows import router as flows_router from langflow.api.v1.components import router as component_router from langflow.api.v1.users import router as users_router from langflow.api.v1.api_key import router as api_key_router +from langflow.api.v1.login import router as login_router __all__ = [ "chat_router", @@ -14,4 +15,5 @@ __all__ = [ "flows_router", "users_router", "api_key_router", + "login_router", ] diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index b559b0a23..600e373df 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -13,7 +13,7 @@ from langflow.services.auth.utils import ( from langflow.services.utils import get_settings_manager -router = APIRouter() +router = APIRouter(tags=["Login"]) @router.post("/login", response_model=Token) From e4cbc0a07ffdc5613dc33b11b571c003a54823bb Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:10:57 -0300 Subject: [PATCH 091/134] =?UTF-8?q?=F0=9F=90=9B=20fix(users.py):=20change?= =?UTF-8?q?=20router=20tag=20from=20"Login"=20to=20"Users"=20for=20better?= =?UTF-8?q?=20categorization=20=E2=9C=A8=20feat(users.py):=20add=20status?= =?UTF-8?q?=20code=20201=20to=20the=20response=20of=20the=20add=5Fuser=20e?= =?UTF-8?q?ndpoint=20to=20indicate=20successful=20creation=20of=20a=20new?= =?UTF-8?q?=20user=20=F0=9F=90=9B=20fix(users.py):=20update=20the=20usage?= =?UTF-8?q?=20of=20UserCreate=20model=20to=20create=20a=20new=20User=20ins?= =?UTF-8?q?tance=20using=20User.from=5Form(user)=20for=20better=20compatib?= =?UTF-8?q?ility=20=E2=9C=A8=20feat(users.py):=20add=20current=5Fuser=20pa?= =?UTF-8?q?rameter=20to=20the=20read=5Fcurrent=5Fuser=20endpoint=20to=20en?= =?UTF-8?q?force=20authentication=20and=20authorization=20=E2=9C=A8=20feat?= =?UTF-8?q?(users.py):=20add=20current=5Fuser=20parameter=20to=20the=20rea?= =?UTF-8?q?d=5Fall=5Fusers=20endpoint=20to=20enforce=20authentication=20an?= =?UTF-8?q?d=20authorization=20=E2=9C=A8=20feat(users.py):=20add=20current?= =?UTF-8?q?=5Fuser=20parameter=20to=20the=20delete=5Fuser=20endpoint=20to?= =?UTF-8?q?=20enforce=20authentication=20and=20authorization.=20Also,=20ad?= =?UTF-8?q?d=20validation=20checks=20to=20prevent=20deleting=20own=20user?= =?UTF-8?q?=20account=20and=20unauthorized=20deletion=20of=20users.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/users.py | 29 +++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index 33ddc9763..5a464b5f2 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -14,15 +14,19 @@ from sqlmodel import Session, select from fastapi import APIRouter, Depends, HTTPException from langflow.services.utils import get_session -from langflow.services.auth.utils import get_current_active_user, get_password_hash +from langflow.services.auth.utils import ( + get_current_active_superuser, + get_current_active_user, + get_password_hash, +) from langflow.services.database.models.user.utils import ( update_user, ) -router = APIRouter(tags=["Login"]) +router = APIRouter(tags=["Users"]) -@router.post("/user", response_model=UserRead) +@router.post("/user", response_model=UserRead, status_code=201) def add_user( user: UserCreate, db: Session = Depends(get_session), @@ -30,7 +34,7 @@ def add_user( """ Add a new user to the database. """ - new_user = User(**user.dict()) + new_user = User.from_orm(user) try: new_user.password = get_password_hash(user.password) @@ -45,7 +49,9 @@ def add_user( @router.get("/user", response_model=UserRead) -def read_current_user(current_user: User = Depends(get_current_active_user)) -> User: +def read_current_user( + current_user: User = Depends(get_current_active_user), +) -> User: """ Retrieve the current user's data. """ @@ -56,7 +62,7 @@ def read_current_user(current_user: User = Depends(get_current_active_user)) -> def read_all_users( skip: int = 0, limit: int = 10, - _: Session = Depends(get_current_active_user), + current_user: Session = Depends(get_current_active_superuser), db: Session = Depends(get_session), ) -> UsersResponse: """ @@ -90,12 +96,21 @@ def patch_user( @router.delete("/user/{user_id}") def delete_user( user_id: UUID, - _: Session = Depends(get_current_active_user), + current_user: Session = Depends(get_current_active_superuser), db: Session = Depends(get_session), ) -> dict: """ Delete a user from the database. """ + if current_user.id == user_id: + raise HTTPException( + status_code=400, detail="You can't delete your own user account" + ) + elif not current_user.is_superuser: + raise HTTPException( + status_code=403, detail="You don't have the permission to delete this user" + ) + user_db = db.query(User).filter(User.id == user_id).first() if not user_db: raise HTTPException(status_code=404, detail="User not found") From 4517f8ad5c3b5f5cbcee99bdc4ee19b68f9e5f23 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:11:24 -0300 Subject: [PATCH 092/134] =?UTF-8?q?=F0=9F=94=A7=20fix(service.py):=20add?= =?UTF-8?q?=20request=20parameter=20to=20run=5Foauth2=5Fscheme=20method=20?= =?UTF-8?q?in=20AuthManager=20class=20to=20handle=20FastAPI=20request=20ob?= =?UTF-8?q?ject=20=F0=9F=94=A7=20fix(utils.py):=20add=20request=20paramete?= =?UTF-8?q?r=20to=20auth=5Fscheme=5Fdependency=20function=20to=20handle=20?= =?UTF-8?q?FastAPI=20request=20object=20=F0=9F=94=A7=20fix(utils.py):=20ch?= =?UTF-8?q?ange=20get=5Fcurrent=5Factive=5Fuser=20function=20to=20synchron?= =?UTF-8?q?ous=20and=20remove=20async=20keyword=20=E2=9C=A8=20feat(utils.p?= =?UTF-8?q?y):=20add=20get=5Fcurrent=5Factive=5Fsuperuser=20function=20to?= =?UTF-8?q?=20check=20if=20the=20current=20user=20is=20an=20active=20super?= =?UTF-8?q?user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/auth/service.py | 5 ++-- src/backend/langflow/services/auth/utils.py | 26 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/backend/langflow/services/auth/service.py b/src/backend/langflow/services/auth/service.py index 57c586c94..c80b984bb 100644 --- a/src/backend/langflow/services/auth/service.py +++ b/src/backend/langflow/services/auth/service.py @@ -1,3 +1,4 @@ +from fastapi import Request from langflow.services.base import Service from typing import TYPE_CHECKING @@ -13,5 +14,5 @@ class AuthManager(Service): # We need to define a function that can be passed to the Depends() function. # This function will be called by FastAPI to run oauth2_scheme - def run_oauth2_scheme(self, *args, **kwargs): - return self.settings_manager.auth_settings.oauth2_scheme(*args, **kwargs) + def run_oauth2_scheme(self, request: Request): + return self.settings_manager.auth_settings.oauth2_scheme(request=request) diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index aa4bec669..ae6065beb 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta, timezone -from fastapi import Depends, HTTPException, status +from fastapi import Depends, HTTPException, Request, status from jose import JWTError, jwt from typing import Annotated from uuid import UUID @@ -14,12 +14,12 @@ from langflow.services.utils import get_session, get_settings_manager from sqlalchemy.orm import Session -def auth_scheme_dependency(*args, **kwargs): +def auth_scheme_dependency(request: Request): settings_manager = ( get_settings_manager() ) # Assuming get_settings_manager is defined - return AuthManager(settings_manager).run_oauth2_scheme(*args, **kwargs) + return AuthManager(settings_manager).run_oauth2_scheme(request) async def get_current_user( @@ -35,7 +35,7 @@ async def get_current_user( ) try: payload = jwt.decode( - token, + await token, settings_manager.auth_settings.SECRET_KEY, algorithms=[settings_manager.auth_settings.ALGORITHM], ) @@ -48,19 +48,29 @@ async def get_current_user( raise credentials_exception from e user = get_user_by_id(db, user_id) # type: ignore - if user is None: + if user is None or not user.is_active: raise credentials_exception return user -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)] -): +def get_current_active_user(current_user: Annotated[User, Depends(get_current_user)]): if not current_user.is_active: raise HTTPException(status_code=400, detail="Inactive user") return current_user +def get_current_active_superuser( + current_user: Annotated[User, Depends(get_current_user)] +) -> User: + if not current_user.is_active: + raise HTTPException(status_code=401, detail="Inactive user") + if not current_user.is_superuser: + raise HTTPException( + status_code=400, detail="The user doesn't have enough privileges" + ) + return current_user + + def verify_password(plain_password, hashed_password): settings_manager = get_settings_manager() return settings_manager.auth_settings.pwd_context.verify( From 92a7ae6be71c7cf6718db20232f75a55e0083a2e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:16:04 -0300 Subject: [PATCH 093/134] =?UTF-8?q?=F0=9F=90=9B=20fix(utils.py):=20remove?= =?UTF-8?q?=20unnecessary=20type=20casting=20in=20get=5Fuser=5Fby=5Fuserna?= =?UTF-8?q?me=20and=20get=5Fuser=5Fby=5Fid=20functions=20=F0=9F=90=9B=20fi?= =?UTF-8?q?x(utils.py):=20fix=20update=5Fuser=20function=20to=20correctly?= =?UTF-8?q?=20update=20user=20attributes=20and=20handle=20username=20confl?= =?UTF-8?q?icts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/database/models/user/utils.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/backend/langflow/services/database/models/user/utils.py b/src/backend/langflow/services/database/models/user/utils.py index 3b600bd9a..514ca4e82 100644 --- a/src/backend/langflow/services/database/models/user/utils.py +++ b/src/backend/langflow/services/database/models/user/utils.py @@ -4,41 +4,41 @@ from fastapi import Depends, HTTPException from langflow.services.database.models.user.user import User, UserUpdate from langflow.services.utils import get_session from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session +from sqlmodel import Session + + +from sqlalchemy.orm.attributes import flag_modified def get_user_by_username(db: Session, username: str) -> User: - db_user = db.query(User).filter(User.username == username).first() - return User.from_orm(db_user) if db_user else None # type: ignore + return db.query(User).filter(User.username == username).first() def get_user_by_id(db: Session, id: UUID) -> User: - db_user = db.query(User).filter(User.id == id).first() - return User.from_orm(db_user) if db_user else None # type: ignore + return db.query(User).filter(User.id == id).first() def update_user( user_id: UUID, user: UserUpdate, db: Session = Depends(get_session) ) -> User: - user_db = get_user_by_username(db, user.username) # type: ignore - if user_db and user_db.id != user_id: - raise HTTPException(status_code=409, detail="Username already exists") - user_db = get_user_by_id(db, user_id) if not user_db: raise HTTPException(status_code=404, detail="User not found") + user_db_by_username = get_user_by_username(db, user.username) # type: ignore + if user_db_by_username and user_db_by_username.id != user_id: + raise HTTPException(status_code=409, detail="Username already exists") + + user_data = user.dict(exclude_unset=True) + for attr, value in user_data.items(): + if hasattr(user_db, attr) and value is not None: + setattr(user_db, attr, value) + + user_db.updated_at = datetime.now(timezone.utc) + flag_modified(user_db, "updated_at") + try: - user_data = user.dict(exclude_unset=True) - for key, value in user_data.items(): - setattr(user_db, key, value) - - user_db.updated_at = datetime.now(timezone.utc) - user_db = db.merge(user_db) db.commit() - if db.identity_key(instance=user_db) is not None: - db.refresh(user_db) - except IntegrityError as e: db.rollback() raise HTTPException(status_code=400, detail=str(e)) from e From 7e3d329df9f0ed8b71794b6b3cccd1bf92f8ec27 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:17:25 -0300 Subject: [PATCH 094/134] =?UTF-8?q?=F0=9F=94=A7=20chore(auth.py):=20add=20?= =?UTF-8?q?API=5FV1=5FSTR=20constant=20to=20improve=20code=20readability?= =?UTF-8?q?=20and=20maintainability=20=F0=9F=90=9B=20fix(auth.py):=20updat?= =?UTF-8?q?e=20tokenUrl=20in=20oauth2=5Fscheme=20to=20use=20API=5FV1=5FSTR?= =?UTF-8?q?=20constant=20for=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/settings/auth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/services/settings/auth.py b/src/backend/langflow/services/settings/auth.py index a00ebf31e..2aa4e17bc 100644 --- a/src/backend/langflow/services/settings/auth.py +++ b/src/backend/langflow/services/settings/auth.py @@ -18,6 +18,7 @@ class AuthSettings(BaseSettings): str ] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a" API_KEY_ALGORITHM: str = "HS256" + API_V1_STR: str = "/api/v1" # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. @@ -26,7 +27,7 @@ class AuthSettings(BaseSettings): FIRST_SUPERUSER_PASSWORD: str = "langflow" pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") + oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{API_V1_STR}/login") class Config: validate_assignment = True From 7b897906b9dcf660dc0ccbb7677d9d76b4639d73 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:17:36 -0300 Subject: [PATCH 095/134] =?UTF-8?q?=E2=9C=A8=20feat(test=5Flogin.py):=20ad?= =?UTF-8?q?d=20tests=20for=20login=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐛 fix(test_login.py): fix typo in test_login_unsuccessful_wrong_username test 🐛 fix(test_login.py): fix typo in test_login_unsuccessful_wrong_password test --- tests/test_login.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_login.py diff --git a/tests/test_login.py b/tests/test_login.py new file mode 100644 index 000000000..07abb35ab --- /dev/null +++ b/tests/test_login.py @@ -0,0 +1,47 @@ +import pytest +from langflow.services.database.models.user import User +from langflow.services.auth.utils import get_password_hash + + +@pytest.fixture +def test_user(): + return User( + username="testuser", + password=get_password_hash( + "testpassword" + ), # Assuming password needs to be hashed + is_active=True, + is_superuser=False, + ) + + +def test_login_successful(client, test_user, session): + # Adding the test user to the database + session.add(test_user) + session.commit() + + response = client.post( + "api/v1/login", data={"username": "testuser", "password": "testpassword"} + ) + assert response.status_code == 200 + assert "access_token" in response.json() + + +def test_login_unsuccessful_wrong_username(client): + response = client.post( + "api/v1/login", data={"username": "wrongusername", "password": "testpassword"} + ) + assert response.status_code == 401 + assert response.json()["detail"] == "Incorrect username or password" + + +def test_login_unsuccessful_wrong_password(client, test_user, session): + # Adding the test user to the database + session.add(test_user) + session.commit() + + response = client.post( + "api/v1/login", data={"username": "testuser", "password": "wrongpassword"} + ) + assert response.status_code == 401 + assert response.json()["detail"] == "Incorrect username or password" From 8411922a07d8376c704062e03ed93a04f006d860 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:17:46 -0300 Subject: [PATCH 096/134] =?UTF-8?q?=E2=9C=A8=20feat(test=5Fuser.py):=20add?= =?UTF-8?q?=20tests=20for=20user-related=20functionalities=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(test=5Fuser.py):=20fix=20typo=20in=20test=5Fuser=5Fwaiti?= =?UTF-8?q?ng=5Ffor=5Fapproval=20function=20name=20=F0=9F=90=9B=20fix(test?= =?UTF-8?q?=5Fuser.py):=20fix=20typo=20in=20test=5Fdeactivated=5Fuser=5Fca?= =?UTF-8?q?nnot=5Faccess=20function=20name=20=F0=9F=90=9B=20fix(test=5Fuse?= =?UTF-8?q?r.py):=20fix=20typo=20in=20test=5Fdata=5Fconsistency=5Fafter=5F?= =?UTF-8?q?update=20function=20name=20=F0=9F=90=9B=20fix(test=5Fuser.py):?= =?UTF-8?q?=20fix=20typo=20in=20test=5Fdata=5Fconsistency=5Fafter=5Fdelete?= =?UTF-8?q?=20function=20name=20=F0=9F=90=9B=20fix(test=5Fuser.py):=20fix?= =?UTF-8?q?=20typo=20in=20test=5Finactive=5Fuser=20function=20name=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(test=5Fuser.py):=20fix=20typo=20in=20test=5F?= =?UTF-8?q?normal=5Fuser=5Fcant=5Fread=5Fall=5Fusers=20function=20name=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(test=5Fuser.py):=20fix=20typo=20in=20test=5F?= =?UTF-8?q?patch=5Fuser=5Fwrong=5Fid=20function=20name=20=F0=9F=90=9B=20fi?= =?UTF-8?q?x(test=5Fuser.py):=20fix=20typo=20in=20test=5Fdelete=5Fuser=5Fw?= =?UTF-8?q?rong=5Fid=20function=20name=20=F0=9F=90=9B=20fix(test=5Fuser.py?= =?UTF-8?q?):=20fix=20typo=20in=20test=5Fnormal=5Fuser=5Fcant=5Fdelete=5Fu?= =?UTF-8?q?ser=20function=20name=20=E2=9C=A8=20feat(test=5Fuser.py):=20add?= =?UTF-8?q?=20test=5Fadd=5Fsuper=5Fuser=5Ffor=5Ftesting=5Fpurposes=5Fdelet?= =?UTF-8?q?e=5Fme=5Fbefore=5Fmerge=5Finto=5Fdev=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_user.py | 248 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 tests/test_user.py diff --git a/tests/test_user.py b/tests/test_user.py new file mode 100644 index 000000000..f8d4ff788 --- /dev/null +++ b/tests/test_user.py @@ -0,0 +1,248 @@ +from datetime import datetime +from langflow.services.auth.utils import create_super_user, get_password_hash + +from langflow.services.database.models.user.user import User +from langflow.services.utils import get_settings_manager +import pytest +from langflow.services.database.models.user import UserCreate, UserUpdate + + +@pytest.fixture +def test_user(client): + user_data = UserCreate( + username="testuser", + password="testpassword", + ) + response = client.post("/api/v1/user", json=user_data.dict()) + return response.json() + + +@pytest.fixture(scope="function") +def active_user(session): + user = User( + username="activeuser", + password=get_password_hash( + "testpassword" + ), # Assuming password needs to be hashed + is_active=True, + is_superuser=False, + ) + session.add(user) + session.commit() + return user + + +@pytest.fixture +def logged_in_headers(client, active_user): + login_data = {"username": active_user.username, "password": "testpassword"} + response = client.post("/api/v1/login", data=login_data) + assert response.status_code == 200 + tokens = response.json() + a_token = tokens["access_token"] + return {"Authorization": f"Bearer {a_token}"} + + +@pytest.fixture +def super_user(client, session): + return create_super_user(session) + + +@pytest.fixture +def super_user_headers(client, super_user): + settings_manager = get_settings_manager() + auth_settings = settings_manager.auth_settings + login_data = { + "username": auth_settings.FIRST_SUPERUSER, + "password": auth_settings.FIRST_SUPERUSER_PASSWORD, + } + response = client.post("/api/v1/login", data=login_data) + assert response.status_code == 200 + tokens = response.json() + a_token = tokens["access_token"] + return {"Authorization": f"Bearer {a_token}"} + + +@pytest.fixture +def deactivated_user(session): + user = User( + username="deactivateduser", + password=get_password_hash("testpassword"), + is_active=False, + is_superuser=False, + last_login_at=datetime.now(), + ) + session.add(user) + session.commit() + return user + + +def test_user_waiting_for_approval(client, session): + # Create a user that is not active and has never logged in + user = User( + username="waitingforapproval", + password=get_password_hash("testpassword"), + is_active=False, + last_login_at=None, + ) + session.add(user) + session.commit() + + login_data = {"username": "waitingforapproval", "password": "testpassword"} + response = client.post("/api/v1/login", data=login_data) + assert response.status_code == 400 + assert response.json()["detail"] == "Waiting for approval" + + +def test_deactivated_user_cannot_login(client, deactivated_user): + login_data = {"username": deactivated_user.username, "password": "testpassword"} + response = client.post("/api/v1/login", data=login_data) + assert response.status_code == 400, response.json() + assert response.json()["detail"] == "Inactive user" + + +def test_deactivated_user_cannot_access(client, deactivated_user, logged_in_headers): + # Assuming the headers for deactivated_user + response = client.get("/api/v1/users", headers=logged_in_headers) + assert response.status_code == 400, response.json() + assert response.json()["detail"] == "The user doesn't have enough privileges" + + +def test_data_consistency_after_update(client, active_user, logged_in_headers): + user_id = active_user.id + update_data = UserUpdate(username="newname") + + response = client.patch( + f"/api/v1/user/{user_id}", json=update_data.dict(), headers=logged_in_headers + ) + assert response.status_code == 200 + + # Fetch the updated user from the database + response = client.get("/api/v1/user", headers=logged_in_headers) + assert response.json()["username"] == "newname", response.json() + + +def test_data_consistency_after_delete(client, test_user, super_user_headers): + user_id = test_user["id"] + response = client.delete(f"/api/v1/user/{user_id}", headers=super_user_headers) + assert response.status_code == 200 + + # Attempt to fetch the deleted user from the database + response = client.get("/api/v1/users", headers=super_user_headers) + assert response.status_code == 200 + assert all(user["id"] != user_id for user in response.json()["users"]) + + +def test_inactive_user(client, session): + # Create a user that is not active and has a last_login_at value + user = User( + username="inactiveuser", + password=get_password_hash("testpassword"), + is_active=False, + last_login_at="2023-01-01T00:00:00", # Set to a valid datetime string + ) + session.add(user) + session.commit() + + login_data = {"username": "inactiveuser", "password": "testpassword"} + response = client.post("/api/v1/login", data=login_data) + assert response.status_code == 400 + assert response.json()["detail"] == "Inactive user" + + +def test_add_user(client, test_user): + assert test_user["username"] == "testuser" + + +# This is not used in the Frontend at the moment +# def test_read_current_user(client: TestClient, active_user): +# # First we need to login to get the access token +# login_data = {"username": "testuser", "password": "testpassword"} +# response = client.post("/api/v1/login", data=login_data) +# assert response.status_code == 200 + +# headers = {"Authorization": f"Bearer {response.json()['access_token']}"} + +# response = client.get("/api/v1/user", headers=headers) +# assert response.status_code == 200, response.json() +# assert response.json()["username"] == "testuser" + + +def test_read_all_users(client, super_user_headers): + response = client.get("/api/v1/users", headers=super_user_headers) + assert response.status_code == 200, response.json() + assert isinstance(response.json()["users"], list) + + +def test_normal_user_cant_read_all_users(client, logged_in_headers): + response = client.get("/api/v1/users", headers=logged_in_headers) + assert response.status_code == 400, response.json() + assert response.json() == {"detail": "The user doesn't have enough privileges"} + + +def test_patch_user(client, active_user, logged_in_headers): + user_id = active_user.id + update_data = UserUpdate( + username="newname", + ) + + response = client.patch( + f"/api/v1/user/{user_id}", json=update_data.dict(), headers=logged_in_headers + ) + assert response.status_code == 200, response.json() + + +def test_patch_user_wrong_id(client, active_user, logged_in_headers): + user_id = "wrong_id" + update_data = UserUpdate( + username="newname", + ) + + response = client.patch( + f"/api/v1/user/{user_id}", json=update_data.dict(), headers=logged_in_headers + ) + assert response.status_code == 422, response.json() + assert response.json() == { + "detail": [ + { + "loc": ["path", "user_id"], + "msg": "value is not a valid uuid", + "type": "type_error.uuid", + } + ] + } + + +def test_delete_user(client, test_user, super_user_headers): + user_id = test_user["id"] + response = client.delete(f"/api/v1/user/{user_id}", headers=super_user_headers) + assert response.status_code == 200 + assert response.json() == {"detail": "User deleted"} + + +def test_delete_user_wrong_id(client, test_user, super_user_headers): + user_id = "wrong_id" + response = client.delete(f"/api/v1/user/{user_id}", headers=super_user_headers) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "loc": ["path", "user_id"], + "msg": "value is not a valid uuid", + "type": "type_error.uuid", + } + ] + } + + +def test_normal_user_cant_delete_user(client, test_user, logged_in_headers): + user_id = test_user["id"] + response = client.delete(f"/api/v1/user/{user_id}", headers=logged_in_headers) + assert response.status_code == 400 + assert response.json() == {"detail": "The user doesn't have enough privileges"} + + +# If you still want to test the superuser endpoint +def test_add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(client): + response = client.post("/api/v1/super_user") + assert response.status_code == 200 + assert response.json()["username"] == "superuser" From 81816baadae57a70c10772397ec9848a6fd2d277 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:24:01 -0300 Subject: [PATCH 097/134] =?UTF-8?q?=F0=9F=94=A7=20fix(login.py):=20update?= =?UTF-8?q?=20import=20statement=20from=20sqlalchemy.orm=20to=20sqlmodel.S?= =?UTF-8?q?ession=20for=20Session=20class=20=F0=9F=94=A7=20fix(users.py):?= =?UTF-8?q?=20update=20type=20annotation=20for=20current=5Fuser=20paramete?= =?UTF-8?q?r=20from=20Session=20to=20User=20=F0=9F=94=A7=20fix(utils.py):?= =?UTF-8?q?=20update=20import=20statement=20from=20sqlalchemy.orm=20to=20s?= =?UTF-8?q?qlmodel.Session=20=F0=9F=94=A7=20fix(utils.py):=20update=20type?= =?UTF-8?q?=20annotation=20for=20token=20parameter=20from=20Annotated=20to?= =?UTF-8?q?=20Union[Coroutine,=20str]=20=F0=9F=94=A7=20fix(utils.py):=20up?= =?UTF-8?q?date=20type=20annotation=20for=20get=5Fuser=5Fby=5Fusername=20f?= =?UTF-8?q?unction=20return=20type=20from=20User=20to=20Union[User,=20None?= =?UTF-8?q?]=20=F0=9F=94=A7=20fix(utils.py):=20update=20type=20annotation?= =?UTF-8?q?=20for=20get=5Fuser=5Fby=5Fid=20function=20return=20type=20from?= =?UTF-8?q?=20User=20to=20Union[User,=20None]=20=F0=9F=94=A7=20fix(manager?= =?UTF-8?q?.py):=20update=20type=20annotation=20for=20dependencies=20param?= =?UTF-8?q?eter=20in=20register=5Ffactory=20method=20from=20List=20to=20Op?= =?UTF-8?q?tional[List]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/login.py | 2 +- src/backend/langflow/api/v1/users.py | 2 +- src/backend/langflow/services/auth/utils.py | 10 +++++++--- .../langflow/services/database/models/user/utils.py | 5 +++-- src/backend/langflow/services/manager.py | 6 ++++-- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index 600e373df..a11167a40 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -1,4 +1,4 @@ -from sqlalchemy.orm import Session +from sqlmodel import Session from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index 5a464b5f2..e41551e8c 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -96,7 +96,7 @@ def patch_user( @router.delete("/user/{user_id}") def delete_user( user_id: UUID, - current_user: Session = Depends(get_current_active_superuser), + current_user: User = Depends(get_current_active_superuser), db: Session = Depends(get_session), ) -> dict: """ diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index ae6065beb..540b012b1 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta, timezone from fastapi import Depends, HTTPException, Request, status from jose import JWTError, jwt -from typing import Annotated +from typing import Annotated, Coroutine from uuid import UUID from langflow.services.auth.service import AuthManager from langflow.services.database.models.user.user import User @@ -11,7 +11,7 @@ from langflow.services.database.models.user.utils import ( update_user_last_login_at, ) from langflow.services.utils import get_session, get_settings_manager -from sqlalchemy.orm import Session +from sqlmodel import Session def auth_scheme_dependency(request: Request): @@ -33,9 +33,13 @@ async def get_current_user( detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) + + if isinstance(token, Coroutine): + token = await token + try: payload = jwt.decode( - await token, + token, settings_manager.auth_settings.SECRET_KEY, algorithms=[settings_manager.auth_settings.ALGORITHM], ) diff --git a/src/backend/langflow/services/database/models/user/utils.py b/src/backend/langflow/services/database/models/user/utils.py index 514ca4e82..3dc02a499 100644 --- a/src/backend/langflow/services/database/models/user/utils.py +++ b/src/backend/langflow/services/database/models/user/utils.py @@ -1,4 +1,5 @@ from datetime import datetime, timezone +from typing import Union from uuid import UUID from fastapi import Depends, HTTPException from langflow.services.database.models.user.user import User, UserUpdate @@ -10,11 +11,11 @@ from sqlmodel import Session from sqlalchemy.orm.attributes import flag_modified -def get_user_by_username(db: Session, username: str) -> User: +def get_user_by_username(db: Session, username: str) -> Union[User, None]: return db.query(User).filter(User.username == username).first() -def get_user_by_id(db: Session, id: UUID) -> User: +def get_user_by_id(db: Session, id: UUID) -> Union[User, None]: return db.query(User).filter(User.id == id).first() diff --git a/src/backend/langflow/services/manager.py b/src/backend/langflow/services/manager.py index 1592e7612..e9895adab 100644 --- a/src/backend/langflow/services/manager.py +++ b/src/backend/langflow/services/manager.py @@ -1,5 +1,5 @@ from langflow.services.schema import ServiceType -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: from langflow.services.factory import ServiceFactory @@ -16,7 +16,9 @@ class ServiceManager: self.dependencies = {} def register_factory( - self, service_factory: "ServiceFactory", dependencies: List[ServiceType] = None + self, + service_factory: "ServiceFactory", + dependencies: Optional[List[ServiceType]] = None, ): """ Registers a new factory with dependencies. From fc93e4ced0235b9506837d0922590fe2cb057a85 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:24:33 -0300 Subject: [PATCH 098/134] =?UTF-8?q?=F0=9F=94=80=20chore(test=5Fdatabase.py?= =?UTF-8?q?):=20update=20import=20statement=20for=20Session=20from=20sqlal?= =?UTF-8?q?chemy.orm=20to=20sqlmodel=20to=20match=20the=20library=20being?= =?UTF-8?q?=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_database.py b/tests/test_database.py index 52a5daa4c..996d4faa5 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -2,7 +2,7 @@ import json import pytest from uuid import UUID, uuid4 -from sqlalchemy.orm import Session +from sqlmodel import Session from fastapi.testclient import TestClient From 8d1e1b871044260c8b734a144888d210458afb06 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 10:25:28 -0300 Subject: [PATCH 099/134] =?UTF-8?q?=F0=9F=94=92=20chore(api=5Fkey.py):=20u?= =?UTF-8?q?pdate=20hardcoded=20API=20key=20value=20to=20improve=20security?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔒 chore(api_key.py): update hardcoded API key value to improve security by removing the actual key from the code and adding a placeholder comment --- src/backend/langflow/api/v1/api_key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/api_key.py b/src/backend/langflow/api/v1/api_key.py index 9fa6acec5..56deb3059 100644 --- a/src/backend/langflow/api/v1/api_key.py +++ b/src/backend/langflow/api/v1/api_key.py @@ -40,7 +40,7 @@ def create_api_key(user_id: str): return { "user_id": user_id, "name": "my api-key 01", - "api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg", + "api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg", # noqa } From eb02220aadc6e4c0ab20923c206a8c42395cb205 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:02:32 -0300 Subject: [PATCH 100/134] =?UTF-8?q?=E2=9C=A8=20feat(api=5Fkey.py):=20add?= =?UTF-8?q?=20GET=20and=20POST=20routes=20for=20retrieving=20and=20creatin?= =?UTF-8?q?g=20API=20keys=20=F0=9F=90=9B=20fix(api=5Fkey.py):=20fix=20dele?= =?UTF-8?q?te=5Fapi=5Fkey=20route=20to=20use=20correct=20dependencies=20an?= =?UTF-8?q?d=20handle=20exceptions=20properly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/api_key.py | 87 ++++++++++++++------------ 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/src/backend/langflow/api/v1/api_key.py b/src/backend/langflow/api/v1/api_key.py index 56deb3059..e7b2dd163 100644 --- a/src/backend/langflow/api/v1/api_key.py +++ b/src/backend/langflow/api/v1/api_key.py @@ -1,49 +1,58 @@ -from fastapi import APIRouter +from fastapi import APIRouter, HTTPException, Depends +from langflow.api.v1.schemas import ApiKeysResponse, CreateApiKeyRequest +from langflow.services.auth.utils import get_current_active_user +from langflow.services.database.models.api_key.api_key import UnmaskedApiKeyRead + +# Assuming you have these methods in your service layer +from langflow.services.database.models.api_key.crud import ( + get_api_keys, + create_api_key, + delete_api_key, +) +from langflow.services.database.models.user.user import User +from langflow.services.utils import get_session +from sqlmodel import Session router = APIRouter(tags=["APIKey"]) -@router.get("/api_key/{user_id}") -def get_api_key(user_id: str): - return { - "total_count": 3, - "user_id": user_id, - "api_keys": [ - { - "id": "4425707e-cce4-4d1b-a54e-bd2632064657", - "api_key": "lf-...abcd", - "name": "my api_key name - 01", - "created_at": "2023-08-15T19:28:40.019613", - "last_used_at": "2023-08-16T18:38:20.875210", - }, - { - "id": "6fb7282b-9f2e-4efe-9bda-0c3d8f899473", - "api_key": "lf-...abcd", - "name": "my api_key name - 02", - "created_at": "2023-08-15T19:41:30.077942", - "last_used_at": "2023-08-15T19:45:32.067899", - }, - { - "id": "c55f3b32-4920-42b6-a5cd-698b4251806e", - "api_key": "lf-...abcd", - "name": "my api_key name - 03", - "created_at": "2023-08-15T20:29:40.577808", - "last_used_at": "2023-08-15T20:29:40.577816", - }, - ], - } +@router.get("/api_key", response_model=ApiKeysResponse) +def get_api_keys_route( + db: Session = Depends(get_session), + current_user: User = Depends(get_current_active_user), +): + try: + user_id = current_user.id + keys = get_api_keys(db, user_id) + + result = {"total_count": len(keys), "user_id": user_id, "api_keys": keys} + return ApiKeysResponse(**result) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) -@router.post("/api_key/{user_id}") -def create_api_key(user_id: str): - return { - "user_id": user_id, - "name": "my api-key 01", - "api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg", # noqa - } +@router.post("/api_key", response_model=UnmaskedApiKeyRead) +def create_api_key_route( + req: CreateApiKeyRequest, + current_user: User = Depends(get_current_active_user), + db: Session = Depends(get_session), +): + try: + user_id = current_user.id + return create_api_key(db, req, user_id=user_id) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) from e @router.delete("/api_key/{api_key_id}") -def delete_api_key(api_key_id: str): - return {"detail": "API Key deleted"} +def delete_api_key_route( + api_key_id: str, + current_user=Depends(get_current_active_user), + db: Session = Depends(get_session), +): + try: + delete_api_key(db, api_key_id) + return {"detail": "API Key deleted"} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) From edd58705e4c4962c9d5cc200a99cd771827cb79e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:03:03 -0300 Subject: [PATCH 101/134] =?UTF-8?q?=F0=9F=94=A8=20refactor(schemas.py):=20?= =?UTF-8?q?update=20import=20statement=20for=20ApiKeyRead=20to=20reflect?= =?UTF-8?q?=20new=20file=20structure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔨 refactor(schemas.py): update import statement for ApiKeyRead to reflect --- src/backend/langflow/api/v1/schemas.py | 27 +++++++++++++++------ src/backend/langflow/api/v1/users.py | 2 +- src/backend/langflow/services/auth/utils.py | 2 +- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index d788469fa..3c138610c 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -1,7 +1,8 @@ from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Union -from langflow.services.database.models.api_key import ApiKeyRead +from uuid import UUID +from langflow.services.database.models.api_key.api_key import ApiKeyRead from langflow.services.database.models.flow import FlowCreate, FlowRead from langflow.services.database.models.user import UserRead from pydantic import BaseModel, Field, validator @@ -138,12 +139,24 @@ class ComponentListRead(BaseModel): flows: List[FlowRead] -class ApiKeyResponse(BaseModel): - total_count: int - user_id: str - api_keys: List[ApiKeyRead] - - class UsersResponse(BaseModel): total_count: int users: List[UserRead] + + +class ApiKeyResponse(BaseModel): + id: str + api_key: str + name: str + created_at: str + last_used_at: str + + +class ApiKeysResponse(BaseModel): + total_count: int + user_id: UUID + api_keys: List[ApiKeyRead] + + +class CreateApiKeyRequest(BaseModel): + name: str diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index e41551e8c..140ee773f 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -19,7 +19,7 @@ from langflow.services.auth.utils import ( get_current_active_user, get_password_hash, ) -from langflow.services.database.models.user.utils import ( +from langflow.services.database.models.user.crud import ( update_user, ) diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index 540b012b1..8cc67d216 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -5,7 +5,7 @@ from typing import Annotated, Coroutine from uuid import UUID from langflow.services.auth.service import AuthManager from langflow.services.database.models.user.user import User -from langflow.services.database.models.user.utils import ( +from langflow.services.database.models.user.crud import ( get_user_by_id, get_user_by_username, update_user_last_login_at, From 82fbeace068a09a83e1c8d02f958a1e40643c39b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:03:51 -0300 Subject: [PATCH 102/134] =?UTF-8?q?=F0=9F=94=80=20chore(api=5Fkey):=20upda?= =?UTF-8?q?te=20import=20and=20export=20names=20in=20=5F=5Finit=5F=5F.py?= =?UTF-8?q?=20for=20better=20clarity=20and=20consistency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔀 chore(api_key): add UnmaskedApiKeyRead model to represent an unmasked API key 🔀 chore(api_key): add user relationship to ApiKey model for easier access to associated user 🔀 chore(api_key): add user_id field to ApiKeyBase and ApiKeyCreate models for easier user association 🔀 chore(api_key): add mask_api_key validator to ApiKeyRead model to mask the API key for security reasons --- .../database/models/api_key/__init__.py | 4 +-- .../database/models/api_key/api_key.py | 28 ++++++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/backend/langflow/services/database/models/api_key/__init__.py b/src/backend/langflow/services/database/models/api_key/__init__.py index c97425ee8..fbb8265b9 100644 --- a/src/backend/langflow/services/database/models/api_key/__init__.py +++ b/src/backend/langflow/services/database/models/api_key/__init__.py @@ -1,3 +1,3 @@ -from .api_key import ApiKey, ApiKeyCreate, ApiKeyRead +from .api_key import ApiKey, ApiKeyCreate, UnmaskedApiKeyRead, ApiKeyRead -__all__ = ["ApiKey", "ApiKeyCreate", "ApiKeyRead"] +__all__ = ["ApiKey", "ApiKeyCreate", "UnmaskedApiKeyRead", "ApiKeyRead"] diff --git a/src/backend/langflow/services/database/models/api_key/api_key.py b/src/backend/langflow/services/database/models/api_key/api_key.py index 784b25229..1006b1c0f 100644 --- a/src/backend/langflow/services/database/models/api_key/api_key.py +++ b/src/backend/langflow/services/database/models/api_key/api_key.py @@ -1,24 +1,44 @@ -from sqlmodel import Field +from pydantic import validator +from sqlmodel import Field, Relationship from uuid import UUID, uuid4 -from typing import Optional +from typing import Optional, TYPE_CHECKING from datetime import datetime from langflow.services.database.models.base import SQLModelSerializable +if TYPE_CHECKING: + from langflow.services.database.models.user import User + class ApiKeyBase(SQLModelSerializable): api_key: str = Field(index=True, unique=True) name: Optional[str] = Field(index=True) - create_at: datetime = Field(default_factory=datetime.utcnow) + created_at: datetime = Field(default_factory=datetime.utcnow) last_used_at: Optional[datetime] = Field(default=None) + user_id: UUID = Field() class ApiKey(ApiKeyBase, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) + # User relationship + user_id: UUID = Field(index=True, foreign_key="user.id") + user: "User" = Relationship(back_populates="api_keys") class ApiKeyCreate(ApiKeyBase): - pass + api_key: Optional[str] = None + user_id: Optional[UUID] = None + + +class UnmaskedApiKeyRead(ApiKeyBase): + id: UUID class ApiKeyRead(ApiKeyBase): id: UUID + api_key: Optional[str] = None + user_id: Optional[UUID] = None + + @validator("api_key", always=True) + def mask_api_key(cls, v): + # This validator will always run, and will mask the API key + return f"{'*' * 8}{v[-4:]}" From e42686738ce21d5ad850196aa34a78dee0841f1d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:04:20 -0300 Subject: [PATCH 103/134] =?UTF-8?q?=F0=9F=93=A6=20chore(api=5Fkey/crud.py)?= =?UTF-8?q?:=20add=20CRUD=20functions=20for=20managing=20API=20keys=20in?= =?UTF-8?q?=20the=20database?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds the following functions to the `api_key/crud.py` file: - `get_api_keys`: Retrieves a list of API keys associated with a specific user ID from the database. - `create_api_key`: Generates a random API key, hashes it, and creates a new `ApiKey` object in the database. - `delete_api_key`: Deletes an API key from the database based on its ID. These functions provide the necessary functionality for managing API keys in the application's database. --- .../services/database/models/api_key/crud.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/backend/langflow/services/database/models/api_key/crud.py diff --git a/src/backend/langflow/services/database/models/api_key/crud.py b/src/backend/langflow/services/database/models/api_key/crud.py new file mode 100644 index 000000000..933e7c2b3 --- /dev/null +++ b/src/backend/langflow/services/database/models/api_key/crud.py @@ -0,0 +1,44 @@ +import secrets +from uuid import UUID +from typing import List +from langflow.services.auth.utils import get_password_hash +from sqlmodel import Session, select +from langflow.services.database.models.api_key import ( + ApiKey, + ApiKeyCreate, + UnmaskedApiKeyRead, + ApiKeyRead, +) + + +def get_api_keys(session: Session, user_id: str) -> List[UnmaskedApiKeyRead]: + query = select(ApiKey).where(ApiKey.user_id == user_id) + api_keys = session.exec(query).all() + return [ApiKeyRead.from_orm(api_key) for api_key in api_keys] + + +def create_api_key( + session: Session, api_key_create: ApiKeyCreate, user_id: str +) -> UnmaskedApiKeyRead: + # Generate a random API key with 32 bytes of randomness + generated_api_key = secrets.token_urlsafe(32) + + # hash the API key + hashed_api_key = get_password_hash(generated_api_key) + # Use the generated key to create the ApiKey object + api_key = ApiKey(api_key=hashed_api_key, **api_key_create.dict(), user_id=user_id) + + session.add(api_key) + session.commit() + session.refresh(api_key) + unmasked = UnmaskedApiKeyRead.from_orm(api_key) + unmasked.api_key = generated_api_key + return unmasked + + +def delete_api_key(session: Session, api_key_id: UUID) -> None: + api_key = session.get(ApiKey, api_key_id) + if api_key is None: + raise ValueError("API Key not found") + session.delete(api_key) + session.commit() From 7783f4532ea6efb75fdd082aa6455ecf3633e23a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:04:51 -0300 Subject: [PATCH 104/134] =?UTF-8?q?=F0=9F=93=A6=20chore(user/crud.py):=20a?= =?UTF-8?q?dd=20CRUD=20operations=20for=20User=20model=20in=20database=20?= =?UTF-8?q?=F0=9F=94=92=20fix(user/crud.py):=20add=20validation=20to=20pre?= =?UTF-8?q?vent=20duplicate=20usernames=20during=20user=20update=20?= =?UTF-8?q?=F0=9F=94=84=20refactor(user/crud.py):=20update=20user=20attrib?= =?UTF-8?q?utes=20and=20last=5Flogin=5Fat=20timestamp=20during=20user=20up?= =?UTF-8?q?date=20=F0=9F=90=9B=20fix(user/crud.py):=20handle=20IntegrityEr?= =?UTF-8?q?ror=20during=20user=20update=20and=20rollback=20transaction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/database/models/user/crud.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/backend/langflow/services/database/models/user/crud.py diff --git a/src/backend/langflow/services/database/models/user/crud.py b/src/backend/langflow/services/database/models/user/crud.py new file mode 100644 index 000000000..3dc02a499 --- /dev/null +++ b/src/backend/langflow/services/database/models/user/crud.py @@ -0,0 +1,53 @@ +from datetime import datetime, timezone +from typing import Union +from uuid import UUID +from fastapi import Depends, HTTPException +from langflow.services.database.models.user.user import User, UserUpdate +from langflow.services.utils import get_session +from sqlalchemy.exc import IntegrityError +from sqlmodel import Session + + +from sqlalchemy.orm.attributes import flag_modified + + +def get_user_by_username(db: Session, username: str) -> Union[User, None]: + return db.query(User).filter(User.username == username).first() + + +def get_user_by_id(db: Session, id: UUID) -> Union[User, None]: + return db.query(User).filter(User.id == id).first() + + +def update_user( + user_id: UUID, user: UserUpdate, db: Session = Depends(get_session) +) -> User: + user_db = get_user_by_id(db, user_id) + if not user_db: + raise HTTPException(status_code=404, detail="User not found") + + user_db_by_username = get_user_by_username(db, user.username) # type: ignore + if user_db_by_username and user_db_by_username.id != user_id: + raise HTTPException(status_code=409, detail="Username already exists") + + user_data = user.dict(exclude_unset=True) + for attr, value in user_data.items(): + if hasattr(user_db, attr) and value is not None: + setattr(user_db, attr, value) + + user_db.updated_at = datetime.now(timezone.utc) + flag_modified(user_db, "updated_at") + + try: + db.commit() + except IntegrityError as e: + db.rollback() + raise HTTPException(status_code=400, detail=str(e)) from e + + return user_db + + +def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)): + user_data = UserUpdate(last_login_at=datetime.now(timezone.utc)) # type: ignore + + return update_user(user_id, user_data, db) From ba081e81b68f7a1f76a8664c96a21fa0836e25e2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:05:20 -0300 Subject: [PATCH 105/134] =?UTF-8?q?=F0=9F=94=80=20chore(user.py):=20add=20?= =?UTF-8?q?type=20hinting=20for=20ApiKey=20import=20to=20improve=20code=20?= =?UTF-8?q?readability=20=F0=9F=94=80=20chore(user.py):=20add=20relationsh?= =?UTF-8?q?ip=20between=20User=20and=20ApiKey=20models=20to=20establish=20?= =?UTF-8?q?a=20one-to-many=20relationship?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langflow/services/database/models/user/user.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/services/database/models/user/user.py b/src/backend/langflow/services/database/models/user/user.py index 3a4308b42..b6c27c2dc 100644 --- a/src/backend/langflow/services/database/models/user/user.py +++ b/src/backend/langflow/services/database/models/user/user.py @@ -1,11 +1,14 @@ from langflow.services.database.models.base import SQLModel, SQLModelSerializable -from sqlmodel import Field +from sqlmodel import Field, Relationship from datetime import datetime -from typing import Optional +from typing import Optional, TYPE_CHECKING from uuid import UUID, uuid4 +if TYPE_CHECKING: + from langflow.services.database.models.api_key import ApiKey + class User(SQLModelSerializable, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) @@ -16,6 +19,7 @@ class User(SQLModelSerializable, table=True): create_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) last_login_at: Optional[datetime] = Field() + api_keys: list["ApiKey"] = Relationship(back_populates="user") class UserCreate(SQLModel): From 950b9d179bb6994f9df951ed2070795a033e2bf5 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:05:54 -0300 Subject: [PATCH 106/134] =?UTF-8?q?=F0=9F=94=A5=20refactor(user/utils.py):?= =?UTF-8?q?=20remove=20unused=20code=20and=20imports=20from=20user/utils.p?= =?UTF-8?q?y=20module=20=E2=9C=A8=20feat(test=5Fapi=5Fkey.py):=20add=20tes?= =?UTF-8?q?ts=20for=20API=20key=20creation,=20retrieval,=20and=20deletion?= =?UTF-8?q?=20=E2=9C=A8=20feat(test=5Fuser.py):=20remove=20unused=20fixtur?= =?UTF-8?q?es=20and=20imports=20from=20test=5Fuser.py=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/database/models/user/utils.py | 53 ------------------- tests/conftest.py | 37 +++++++++++++ tests/test_api_key.py | 50 +++++++++++++++++ tests/test_user.py | 37 +------------ 4 files changed, 88 insertions(+), 89 deletions(-) delete mode 100644 src/backend/langflow/services/database/models/user/utils.py create mode 100644 tests/test_api_key.py diff --git a/src/backend/langflow/services/database/models/user/utils.py b/src/backend/langflow/services/database/models/user/utils.py deleted file mode 100644 index 3dc02a499..000000000 --- a/src/backend/langflow/services/database/models/user/utils.py +++ /dev/null @@ -1,53 +0,0 @@ -from datetime import datetime, timezone -from typing import Union -from uuid import UUID -from fastapi import Depends, HTTPException -from langflow.services.database.models.user.user import User, UserUpdate -from langflow.services.utils import get_session -from sqlalchemy.exc import IntegrityError -from sqlmodel import Session - - -from sqlalchemy.orm.attributes import flag_modified - - -def get_user_by_username(db: Session, username: str) -> Union[User, None]: - return db.query(User).filter(User.username == username).first() - - -def get_user_by_id(db: Session, id: UUID) -> Union[User, None]: - return db.query(User).filter(User.id == id).first() - - -def update_user( - user_id: UUID, user: UserUpdate, db: Session = Depends(get_session) -) -> User: - user_db = get_user_by_id(db, user_id) - if not user_db: - raise HTTPException(status_code=404, detail="User not found") - - user_db_by_username = get_user_by_username(db, user.username) # type: ignore - if user_db_by_username and user_db_by_username.id != user_id: - raise HTTPException(status_code=409, detail="Username already exists") - - user_data = user.dict(exclude_unset=True) - for attr, value in user_data.items(): - if hasattr(user_db, attr) and value is not None: - setattr(user_db, attr, value) - - user_db.updated_at = datetime.now(timezone.utc) - flag_modified(user_db, "updated_at") - - try: - db.commit() - except IntegrityError as e: - db.rollback() - raise HTTPException(status_code=400, detail=str(e)) from e - - return user_db - - -def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)): - user_data = UserUpdate(last_login_at=datetime.now(timezone.utc)) # type: ignore - - return update_user(user_id, user_data, db) diff --git a/tests/conftest.py b/tests/conftest.py index e90d03d0a..9abe89d49 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,8 @@ from typing import AsyncGenerator, TYPE_CHECKING from langflow.api.v1.flows import get_session from langflow.graph.graph.base import Graph +from langflow.services.auth.utils import get_password_hash +from langflow.services.database.models.user.user import User, UserCreate import pytest from fastapi.testclient import TestClient from httpx import AsyncClient @@ -155,3 +157,38 @@ def session_getter_fixture(client): @pytest.fixture def runner(): return CliRunner() + + +@pytest.fixture +def test_user(client): + user_data = UserCreate( + username="testuser", + password="testpassword", + ) + response = client.post("/api/v1/user", json=user_data.dict()) + return response.json() + + +@pytest.fixture(scope="function") +def active_user(session): + user = User( + username="activeuser", + password=get_password_hash( + "testpassword" + ), # Assuming password needs to be hashed + is_active=True, + is_superuser=False, + ) + session.add(user) + session.commit() + return user + + +@pytest.fixture +def logged_in_headers(client, active_user): + login_data = {"username": active_user.username, "password": "testpassword"} + response = client.post("/api/v1/login", data=login_data) + assert response.status_code == 200 + tokens = response.json() + a_token = tokens["access_token"] + return {"Authorization": f"Bearer {a_token}"} diff --git a/tests/test_api_key.py b/tests/test_api_key.py new file mode 100644 index 000000000..43b91fa43 --- /dev/null +++ b/tests/test_api_key.py @@ -0,0 +1,50 @@ +import pytest +from langflow.services.database.models.api_key import ApiKeyCreate + + +@pytest.fixture +def api_key(client, logged_in_headers, active_user): + api_key = ApiKeyCreate(name="test-api-key") + + response = client.post( + "api/v1/api_key", data=api_key.json(), headers=logged_in_headers + ) + assert response.status_code == 200, response.text + return response.json() + + +def test_get_api_keys(client, logged_in_headers, api_key): + response = client.get("api/v1/api_key", headers=logged_in_headers) + assert response.status_code == 200, response.text + data = response.json() + assert "total_count" in data + assert "user_id" in data + assert "api_keys" in data + assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"]) + # assert all api keys in data["api_keys"] are masked + assert all("**" in api_key["api_key"] for api_key in data["api_keys"]) + # Add more assertions as needed based on the expected data structure and content + + +def test_create_api_key(client, logged_in_headers): + api_key_name = "test-api-key" + response = client.post( + "api/v1/api_key", json={"name": api_key_name}, headers=logged_in_headers + ) + assert response.status_code == 200 + data = response.json() + assert "name" in data and data["name"] == api_key_name + assert "api_key" in data + # When creating the API key is returned which is + # the only time the API key is unmasked + assert "**" not in data["api_key"] + + +def test_delete_api_key(client, logged_in_headers, active_user, api_key): + # Assuming a function to create a test API key, returning the key ID + api_key_id = api_key["id"] + response = client.delete(f"api/v1/api_key/{api_key_id}", headers=logged_in_headers) + assert response.status_code == 200 + data = response.json() + assert data["detail"] == "API Key deleted" + # Optionally, add a follow-up check to ensure that the key is actually removed from the database diff --git a/tests/test_user.py b/tests/test_user.py index f8d4ff788..d734e4d61 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -4,42 +4,7 @@ from langflow.services.auth.utils import create_super_user, get_password_hash from langflow.services.database.models.user.user import User from langflow.services.utils import get_settings_manager import pytest -from langflow.services.database.models.user import UserCreate, UserUpdate - - -@pytest.fixture -def test_user(client): - user_data = UserCreate( - username="testuser", - password="testpassword", - ) - response = client.post("/api/v1/user", json=user_data.dict()) - return response.json() - - -@pytest.fixture(scope="function") -def active_user(session): - user = User( - username="activeuser", - password=get_password_hash( - "testpassword" - ), # Assuming password needs to be hashed - is_active=True, - is_superuser=False, - ) - session.add(user) - session.commit() - return user - - -@pytest.fixture -def logged_in_headers(client, active_user): - login_data = {"username": active_user.username, "password": "testpassword"} - response = client.post("/api/v1/login", data=login_data) - assert response.status_code == 200 - tokens = response.json() - a_token = tokens["access_token"] - return {"Authorization": f"Bearer {a_token}"} +from langflow.services.database.models.user import UserUpdate @pytest.fixture From a4aeff6d7966d07f7cca11bcc55b03090da51d38 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:21:14 -0300 Subject: [PATCH 107/134] =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20imp?= =?UTF-8?q?ort=20missing=20dependencies=20and=20fix=20type=20annotations?= =?UTF-8?q?=20in=20api=5Fkey.py=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20fix?= =?UTF-8?q?=20type=20annotations=20and=20import=20in=20api=5Fkey.py=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20fix=20type=20annotations=20?= =?UTF-8?q?and?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/api_key.py | 15 +++++++++------ .../services/database/models/api_key/api_key.py | 9 +++++---- .../services/database/models/api_key/crud.py | 7 ++++--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/backend/langflow/api/v1/api_key.py b/src/backend/langflow/api/v1/api_key.py index e7b2dd163..df2d3e420 100644 --- a/src/backend/langflow/api/v1/api_key.py +++ b/src/backend/langflow/api/v1/api_key.py @@ -1,7 +1,11 @@ +from uuid import UUID from fastapi import APIRouter, HTTPException, Depends -from langflow.api.v1.schemas import ApiKeysResponse, CreateApiKeyRequest +from langflow.api.v1.schemas import ApiKeysResponse from langflow.services.auth.utils import get_current_active_user -from langflow.services.database.models.api_key.api_key import UnmaskedApiKeyRead +from langflow.services.database.models.api_key.api_key import ( + ApiKeyCreate, + UnmaskedApiKeyRead, +) # Assuming you have these methods in your service layer from langflow.services.database.models.api_key.crud import ( @@ -26,15 +30,14 @@ def get_api_keys_route( user_id = current_user.id keys = get_api_keys(db, user_id) - result = {"total_count": len(keys), "user_id": user_id, "api_keys": keys} - return ApiKeysResponse(**result) + return ApiKeysResponse(total_count=len(keys), user_id=user_id, api_keys=keys) except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @router.post("/api_key", response_model=UnmaskedApiKeyRead) def create_api_key_route( - req: CreateApiKeyRequest, + req: ApiKeyCreate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_session), ): @@ -47,7 +50,7 @@ def create_api_key_route( @router.delete("/api_key/{api_key_id}") def delete_api_key_route( - api_key_id: str, + api_key_id: UUID, current_user=Depends(get_current_active_user), db: Session = Depends(get_session), ): diff --git a/src/backend/langflow/services/database/models/api_key/api_key.py b/src/backend/langflow/services/database/models/api_key/api_key.py index 1006b1c0f..601d060b5 100644 --- a/src/backend/langflow/services/database/models/api_key/api_key.py +++ b/src/backend/langflow/services/database/models/api_key/api_key.py @@ -10,15 +10,14 @@ if TYPE_CHECKING: class ApiKeyBase(SQLModelSerializable): - api_key: str = Field(index=True, unique=True) name: Optional[str] = Field(index=True) created_at: datetime = Field(default_factory=datetime.utcnow) last_used_at: Optional[datetime] = Field(default=None) - user_id: UUID = Field() class ApiKey(ApiKeyBase, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) + api_key: str = Field(index=True, unique=True) # User relationship user_id: UUID = Field(index=True, foreign_key="user.id") user: "User" = Relationship(back_populates="api_keys") @@ -31,12 +30,14 @@ class ApiKeyCreate(ApiKeyBase): class UnmaskedApiKeyRead(ApiKeyBase): id: UUID + api_key: str = Field(index=True, unique=True) + user_id: UUID = Field() class ApiKeyRead(ApiKeyBase): id: UUID - api_key: Optional[str] = None - user_id: Optional[UUID] = None + api_key: str = Field(index=True, unique=True) + user_id: UUID = Field() @validator("api_key", always=True) def mask_api_key(cls, v): diff --git a/src/backend/langflow/services/database/models/api_key/crud.py b/src/backend/langflow/services/database/models/api_key/crud.py index 933e7c2b3..af697b6d5 100644 --- a/src/backend/langflow/services/database/models/api_key/crud.py +++ b/src/backend/langflow/services/database/models/api_key/crud.py @@ -11,14 +11,14 @@ from langflow.services.database.models.api_key import ( ) -def get_api_keys(session: Session, user_id: str) -> List[UnmaskedApiKeyRead]: +def get_api_keys(session: Session, user_id: UUID) -> List[ApiKeyRead]: query = select(ApiKey).where(ApiKey.user_id == user_id) api_keys = session.exec(query).all() return [ApiKeyRead.from_orm(api_key) for api_key in api_keys] def create_api_key( - session: Session, api_key_create: ApiKeyCreate, user_id: str + session: Session, api_key_create: ApiKeyCreate, user_id: UUID ) -> UnmaskedApiKeyRead: # Generate a random API key with 32 bytes of randomness generated_api_key = secrets.token_urlsafe(32) @@ -26,7 +26,8 @@ def create_api_key( # hash the API key hashed_api_key = get_password_hash(generated_api_key) # Use the generated key to create the ApiKey object - api_key = ApiKey(api_key=hashed_api_key, **api_key_create.dict(), user_id=user_id) + + api_key = ApiKey(api_key=hashed_api_key, name=api_key_create.name, user_id=user_id) session.add(api_key) session.commit() From b5e4df99439c97171d3b12226ee3b0404d48378e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:49:24 -0300 Subject: [PATCH 108/134] =?UTF-8?q?=F0=9F=90=9B=20fix(flows.py):=20add=20m?= =?UTF-8?q?issing=20import=20statement=20for=20User=20model=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(flows.py):=20add=20missing=20import=20statement=20for=20?= =?UTF-8?q?TYPE=5FCHECKING=20=E2=9C=A8=20feat(flows.py):=20add=20user=5Fid?= =?UTF-8?q?=20field=20to=20FlowCreate=20model=20to=20allow=20specifying=20?= =?UTF-8?q?the=20user=20for=20a=20new=20flow=20=E2=9C=A8=20feat(flows.py):?= =?UTF-8?q?=20add=20user=5Fid=20field=20to=20FlowRead=20model=20to=20inclu?= =?UTF-8?q?de=20the=20user=5Fid=20in=20the=20response=20=E2=9C=A8=20feat(f?= =?UTF-8?q?lows.py):=20add=20user=5Fid=20field=20to=20Flow=20model=20and?= =?UTF-8?q?=20create=20a=20relationship=20with=20User=20model=20=E2=9C=A8?= =?UTF-8?q?=20feat(flows.py):=20add=20current=5Fuser=20dependency=20to=20c?= =?UTF-8?q?reate=5Fflow=20endpoint=20to=20set=20the=20user=5Fid=20for=20a?= =?UTF-8?q?=20new=20flow=20=E2=9C=A8=20feat(flows.py):=20add=20current=5Fu?= =?UTF-8?q?ser=20dependency=20to=20read=5Fflows=20endpoint=20to=20filter?= =?UTF-8?q?=20flows=20by=20current=20user=20=E2=9C=A8=20feat(flows.py):=20?= =?UTF-8?q?add=20current=5Fuser=20dependency=20to=20read=5Fflow=20endpoint?= =?UTF-8?q?=20to=20filter=20flow=20by=20current=20user=20=E2=9C=A8=20feat(?= =?UTF-8?q?flows.py):=20add=20current=5Fuser=20dependency=20to=20update=5F?= =?UTF-8?q?flow=20endpoint=20to=20filter=20flow=20by=20current=20user=20?= =?UTF-8?q?=E2=9C=A8=20feat(flows.py):=20add=20current=5Fuser=20dependency?= =?UTF-8?q?=20to=20delete=5Fflow=20endpoint=20to=20filter=20flow=20by=20cu?= =?UTF-8?q?rrent=20user=20=E2=9C=A8=20feat(flows.py):=20add=20current=5Fus?= =?UTF-8?q?er=20dependency=20to=20create=5Fflows=20endpoint=20to=20set=20t?= =?UTF-8?q?he=20user=5Fid=20for=20new=20flows=20=E2=9C=A8=20feat(flows.py)?= =?UTF-8?q?:=20add=20current=5Fuser=20dependency=20to=20upload=5Ffile=20en?= =?UTF-8?q?dpoint=20to=20set=20the=20user=5Fid=20for=20new=20flows=20?= =?UTF-8?q?=E2=9C=A8=20feat(flows.py):=20add=20current=5Fuser=20dependency?= =?UTF-8?q?=20to=20download=5Ffile=20endpoint=20to=20filter=20flows=20by?= =?UTF-8?q?=20current=20user=20=F0=9F=90=9B=20fix(flow.py):=20add=20missin?= =?UTF-8?q?g=20import=20statement=20for=20User=20model=20=E2=9C=A8=20feat(?= =?UTF-8?q?flow.py):=20add=20user=5Fid=20field=20to=20Flow=20model=20to=20?= =?UTF-8?q?associate=20a=20flow=20with=20a=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/flows.py | 86 +++++++++++++++---- .../services/database/models/flow/flow.py | 12 ++- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/backend/langflow/api/v1/flows.py b/src/backend/langflow/api/v1/flows.py index d6c63a707..b215b9f95 100644 --- a/src/backend/langflow/api/v1/flows.py +++ b/src/backend/langflow/api/v1/flows.py @@ -4,16 +4,18 @@ from fastapi.encoders import jsonable_encoder from langflow.api.utils import remove_api_keys from langflow.api.v1.schemas import FlowListCreate, FlowListRead +from langflow.services.auth.utils import get_current_active_user from langflow.services.database.models.flow import ( Flow, FlowCreate, FlowRead, FlowUpdate, ) +from langflow.services.database.models.user.user import User from langflow.services.utils import get_session from langflow.services.utils import get_settings_manager import orjson -from sqlmodel import Session, select +from sqlmodel import Session from fastapi import APIRouter, Depends, HTTPException from fastapi import File, UploadFile @@ -23,9 +25,18 @@ router = APIRouter(prefix="/flows", tags=["Flows"]) @router.post("/", response_model=FlowRead, status_code=201) -def create_flow(*, session: Session = Depends(get_session), flow: FlowCreate): +def create_flow( + *, + session: Session = Depends(get_session), + flow: FlowCreate, + current_user: User = Depends(get_current_active_user), +): """Create a new flow.""" + if flow.user_id is None: + flow.user_id = current_user.id + db_flow = Flow.from_orm(flow) + session.add(db_flow) session.commit() session.refresh(db_flow) @@ -33,31 +44,49 @@ def create_flow(*, session: Session = Depends(get_session), flow: FlowCreate): @router.get("/", response_model=list[FlowRead], status_code=200) -def read_flows(*, session: Session = Depends(get_session)): +def read_flows( + *, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_active_user), +): """Read all flows.""" try: - flows = session.exec(select(Flow)).all() + flows = current_user.flows except Exception as e: raise HTTPException(status_code=500, detail=str(e)) from e return [jsonable_encoder(flow) for flow in flows] @router.get("/{flow_id}", response_model=FlowRead, status_code=200) -def read_flow(*, session: Session = Depends(get_session), flow_id: UUID): +def read_flow( + *, + session: Session = Depends(get_session), + flow_id: UUID, + current_user: User = Depends(get_current_active_user), +): """Read a flow.""" - if flow := session.get(Flow, flow_id): - return flow + if user_flow := ( + session.query(Flow) + .filter(Flow.id == flow_id) + .filter(Flow.user_id == current_user.id) + .first() + ): + return user_flow else: raise HTTPException(status_code=404, detail="Flow not found") @router.patch("/{flow_id}", response_model=FlowRead, status_code=200) def update_flow( - *, session: Session = Depends(get_session), flow_id: UUID, flow: FlowUpdate + *, + session: Session = Depends(get_session), + flow_id: UUID, + flow: FlowUpdate, + current_user: User = Depends(get_current_active_user), ): """Update a flow.""" - db_flow = session.get(Flow, flow_id) + db_flow = read_flow(session=session, flow_id=flow_id, current_user=current_user) if not db_flow: raise HTTPException(status_code=404, detail="Flow not found") flow_data = flow.dict(exclude_unset=True) @@ -65,7 +94,8 @@ def update_flow( if settings_manager.settings.REMOVE_API_KEYS: flow_data = remove_api_keys(flow_data) for key, value in flow_data.items(): - setattr(db_flow, key, value) + if value is not None: + setattr(db_flow, key, value) session.add(db_flow) session.commit() session.refresh(db_flow) @@ -73,9 +103,14 @@ def update_flow( @router.delete("/{flow_id}", status_code=200) -def delete_flow(*, session: Session = Depends(get_session), flow_id: UUID): +def delete_flow( + *, + session: Session = Depends(get_session), + flow_id: UUID, + current_user: User = Depends(get_current_active_user), +): """Delete a flow.""" - flow = session.get(Flow, flow_id) + flow = read_flow(session=session, flow_id=flow_id, current_user=current_user) if not flow: raise HTTPException(status_code=404, detail="Flow not found") session.delete(flow) @@ -87,10 +122,16 @@ def delete_flow(*, session: Session = Depends(get_session), flow_id: UUID): @router.post("/batch/", response_model=List[FlowRead], status_code=201) -def create_flows(*, session: Session = Depends(get_session), flow_list: FlowListCreate): +def create_flows( + *, + session: Session = Depends(get_session), + flow_list: FlowListCreate, + current_user: User = Depends(get_current_active_user), +): """Create multiple new flows.""" db_flows = [] for flow in flow_list.flows: + flow.user_id = current_user.id db_flow = Flow.from_orm(flow) session.add(db_flow) db_flows.append(db_flow) @@ -102,7 +143,10 @@ def create_flows(*, session: Session = Depends(get_session), flow_list: FlowList @router.post("/upload/", response_model=List[FlowRead], status_code=201) async def upload_file( - *, session: Session = Depends(get_session), file: UploadFile = File(...) + *, + session: Session = Depends(get_session), + file: UploadFile = File(...), + current_user: User = Depends(get_current_active_user), ): """Upload flows from a file.""" contents = await file.read() @@ -111,11 +155,19 @@ async def upload_file( flow_list = FlowListCreate(**data) else: flow_list = FlowListCreate(flows=[FlowCreate(**flow) for flow in data]) - return create_flows(session=session, flow_list=flow_list) + # Now we set the user_id for all flows + for flow in flow_list.flows: + flow.user_id = current_user.id + + return create_flows(session=session, flow_list=flow_list, current_user=current_user) @router.get("/download/", response_model=FlowListRead, status_code=200) -async def download_file(*, session: Session = Depends(get_session)): +async def download_file( + *, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_active_user), +): """Download all flows as a file.""" - flows = read_flows(session=session) + flows = read_flows(session=session, current_user=current_user) return FlowListRead(flows=flows) diff --git a/src/backend/langflow/services/database/models/flow/flow.py b/src/backend/langflow/services/database/models/flow/flow.py index a05de5791..e6ad4af4a 100644 --- a/src/backend/langflow/services/database/models/flow/flow.py +++ b/src/backend/langflow/services/database/models/flow/flow.py @@ -2,9 +2,12 @@ from langflow.services.database.models.base import SQLModelSerializable from pydantic import validator -from sqlmodel import Field, JSON, Column +from sqlmodel import Field, JSON, Column, Relationship from uuid import UUID, uuid4 -from typing import Dict, Optional +from typing import Dict, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from langflow.services.database.models.user import User class FlowBase(SQLModelSerializable): @@ -31,14 +34,17 @@ class FlowBase(SQLModelSerializable): class Flow(FlowBase, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) data: Optional[Dict] = Field(default=None, sa_column=Column(JSON)) + user_id: UUID = Field(index=True, foreign_key="user.id") + user: "User" = Relationship(back_populates="flows") class FlowCreate(FlowBase): - pass + user_id: Optional[UUID] = None class FlowRead(FlowBase): id: UUID + user_id: UUID = Field() class FlowUpdate(SQLModelSerializable): From 00e5f70fe1654e70049d76830e9b640a034eabee Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:49:46 -0300 Subject: [PATCH 109/134] =?UTF-8?q?=F0=9F=94=80=20chore(user.py):=20add=20?= =?UTF-8?q?relationship=20to=20flows=20in=20User=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/database/models/user/user.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/langflow/services/database/models/user/user.py b/src/backend/langflow/services/database/models/user/user.py index b6c27c2dc..5f83b4d88 100644 --- a/src/backend/langflow/services/database/models/user/user.py +++ b/src/backend/langflow/services/database/models/user/user.py @@ -8,6 +8,7 @@ from uuid import UUID, uuid4 if TYPE_CHECKING: from langflow.services.database.models.api_key import ApiKey + from langflow.services.database.models.flow import Flow class User(SQLModelSerializable, table=True): @@ -20,6 +21,7 @@ class User(SQLModelSerializable, table=True): updated_at: datetime = Field(default_factory=datetime.utcnow) last_login_at: Optional[datetime] = Field() api_keys: list["ApiKey"] = Relationship(back_populates="user") + flows: list["Flow"] = Relationship(back_populates="user") class UserCreate(SQLModel): From 3b2bfd06cc2091dec706bddceebe7e5e01fd6742 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 12:49:57 -0300 Subject: [PATCH 110/134] =?UTF-8?q?=F0=9F=94=A7=20fix(test=5Fdatabase.py):?= =?UTF-8?q?=20fix=20test=5Fcreate=5Fflow=20function=20signature=20to=20inc?= =?UTF-8?q?lude=20active=5Fuser=20and=20logged=5Fin=5Fheaders=20parameters?= =?UTF-8?q?=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fread?= =?UTF-8?q?=5Fflows=20function=20signature=20to=20include=20active=5Fuser?= =?UTF-8?q?=20and=20logged=5Fin=5Fheaders=20parameters=20=F0=9F=94=A7=20fi?= =?UTF-8?q?x(test=5Fdatabase.py):=20fix=20test=5Fread=5Fflow=20function=20?= =?UTF-8?q?signature=20to=20include=20active=5Fuser=20and=20logged=5Fin=5F?= =?UTF-8?q?headers=20parameters=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):?= =?UTF-8?q?=20fix=20test=5Fupdate=5Fflow=20function=20signature=20to=20inc?= =?UTF-8?q?lude=20active=5Fuser=20and=20logged=5Fin=5Fheaders=20parameters?= =?UTF-8?q?=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fdelet?= =?UTF-8?q?e=5Fflow=20function=20signature=20to=20include=20active=5Fuser?= =?UTF-8?q?=20and=20logged=5Fin=5Fheaders=20parameters=20=F0=9F=94=A7=20fi?= =?UTF-8?q?x(test=5Fdatabase.py):=20fix=20test=5Fcreate=5Fflows=20function?= =?UTF-8?q?=20signature=20to=20include=20logged=5Fin=5Fheaders=20parameter?= =?UTF-8?q?=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fuploa?= =?UTF-8?q?d=5Ffile=20function=20signature=20to=20include=20logged=5Fin=5F?= =?UTF-8?q?headers=20parameter=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):?= =?UTF-8?q?=20fix=20test=5Fdownload=5Ffile=20function=20signature=20to=20i?= =?UTF-8?q?nclude=20active=5Fuser=20and=20logged=5Fin=5Fheaders=20paramete?= =?UTF-8?q?rs=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fcre?= =?UTF-8?q?ate=5Fflow=5Fwith=5Finvalid=5Fdata=20function=20signature=20to?= =?UTF-8?q?=20include=20active=5Fuser=20and=20logged=5Fin=5Fheaders=20para?= =?UTF-8?q?meters=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test?= =?UTF-8?q?=5Fget=5Fnonexistent=5Fflow=20function=20signature=20to=20inclu?= =?UTF-8?q?de=20active=5Fuser=20and=20logged=5Fin=5Fheaders=20parameters?= =?UTF-8?q?=20=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fupdat?= =?UTF-8?q?e=5Fflow=5Fidempotency=20function=20signature=20to=20include=20?= =?UTF-8?q?active=5Fuser=20and=20logged=5Fin=5Fheaders=20parameters=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fupdate?= =?UTF-8?q?=5Fnonexistent=5Fflow=20function=20signature=20to=20include=20a?= =?UTF-8?q?ctive=5Fuser=20and=20logged=5Fin=5Fheaders=20parameters=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fdelete?= =?UTF-8?q?=5Fnonexistent=5Fflow=20function=20signature=20to=20include=20a?= =?UTF-8?q?ctive=5Fuser=20and=20logged=5Fin=5Fheaders=20parameters=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(test=5Fdatabase.py):=20fix=20test=5Fread=5Fe?= =?UTF-8?q?mpty=5Fflows=20function=20signature=20to=20include=20active=5Fu?= =?UTF-8?q?ser=20and=20logged=5Fin=5Fheaders=20parameters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_database.py | 104 +++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index 48e253026..e4f68ca56 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -25,63 +25,69 @@ def json_style(): ) -def test_create_flow(client: TestClient, json_flow: str): +def test_create_flow( + client: TestClient, json_flow: str, active_user, logged_in_headers +): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict()) + response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data # flow is optional so we can create a flow without a flow flow = FlowCreate(name="Test Flow") - response = client.post("api/v1/flows/", json=flow.dict(exclude_unset=True)) + response = client.post( + "api/v1/flows/", json=flow.dict(exclude_unset=True), headers=logged_in_headers + ) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data -def test_read_flows(client: TestClient, json_flow: str): +def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_headers): flow_data = orjson.loads(json_flow) data = flow_data["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict()) + response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict()) + response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data - response = client.get("api/v1/flows/") + response = client.get("api/v1/flows/", headers=logged_in_headers) assert response.status_code == 200 assert len(response.json()) > 0 -def test_read_flow(client: TestClient, json_flow: str): +def test_read_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict()) + response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) flow_id = response.json()["id"] # flow_id should be a UUID but is a string # turn it into a UUID flow_id = UUID(flow_id) - response = client.get(f"api/v1/flows/{flow_id}") + response = client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers) assert response.status_code == 200 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data -def test_update_flow(client: TestClient, json_flow: str): +def test_update_flow( + client: TestClient, json_flow: str, active_user, logged_in_headers +): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict()) + response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) flow_id = response.json()["id"] updated_flow = FlowUpdate( @@ -89,7 +95,9 @@ def test_update_flow(client: TestClient, json_flow: str): description="updated description", data=data, ) - response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.dict()) + response = client.patch( + f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers + ) assert response.status_code == 200 assert response.json()["name"] == updated_flow.name @@ -97,18 +105,22 @@ def test_update_flow(client: TestClient, json_flow: str): # assert response.json()["data"] == updated_flow.data -def test_delete_flow(client: TestClient, json_flow: str): +def test_delete_flow( + client: TestClient, json_flow: str, active_user, logged_in_headers +): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict()) + response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) flow_id = response.json()["id"] - response = client.delete(f"api/v1/flows/{flow_id}") + response = client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers) assert response.status_code == 200 assert response.json()["message"] == "Flow deleted successfully" -def test_create_flows(client: TestClient, session: Session, json_flow: str): +def test_create_flows( + client: TestClient, session: Session, json_flow: str, logged_in_headers +): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -119,7 +131,9 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str): ] ) # Make request to endpoint - response = client.post("api/v1/flows/batch/", json=flow_list.dict()) + response = client.post( + "api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers + ) # Check response status code assert response.status_code == 201 # Check response data @@ -133,7 +147,9 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str): assert response_data[1]["data"] == data -def test_upload_file(client: TestClient, session: Session, json_flow: str): +def test_upload_file( + client: TestClient, session: Session, json_flow: str, logged_in_headers +): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -147,6 +163,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str): response = client.post( "api/v1/flows/upload/", files={"file": ("examples.json", file_contents, "application/json")}, + headers=logged_in_headers, ) # Check response status code assert response.status_code == 201 @@ -161,7 +178,9 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str): assert response_data[1]["data"] == data -def test_download_file(client: TestClient, session: Session, json_flow): +def test_download_file( + client: TestClient, session: Session, json_flow, active_user, logged_in_headers +): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -172,11 +191,12 @@ def test_download_file(client: TestClient, session: Session, json_flow): ] ) for flow in flow_list.flows: + flow.user_id = active_user.id db_flow = Flow.from_orm(flow) session.add(db_flow) session.commit() # Make request to endpoint - response = client.get("api/v1/flows/download/") + response = client.get("api/v1/flows/download/", headers=logged_in_headers) # Check response status code assert response.status_code == 200 # Check response data @@ -190,31 +210,43 @@ def test_download_file(client: TestClient, session: Session, json_flow): assert response_data[1]["data"] == data -def test_create_flow_with_invalid_data(client: TestClient): +def test_create_flow_with_invalid_data( + client: TestClient, active_user, logged_in_headers +): flow = {"name": "a" * 256, "data": "Invalid flow data"} - response = client.post("api/v1/flows/", json=flow) + response = client.post("api/v1/flows/", json=flow, headers=logged_in_headers) assert response.status_code == 422 -def test_get_nonexistent_flow(client: TestClient): +def test_get_nonexistent_flow(client: TestClient, active_user, logged_in_headers): uuid = uuid4() - response = client.get(f"api/v1/flows/{uuid}") + response = client.get(f"api/v1/flows/{uuid}", headers=logged_in_headers) assert response.status_code == 404 -def test_update_flow_idempotency(client: TestClient, json_flow: str): +def test_update_flow_idempotency( + client: TestClient, json_flow: str, active_user, logged_in_headers +): flow_data = orjson.loads(json_flow) data = flow_data["data"] flow_data = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow_data.dict()) + response = client.post( + "api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers + ) flow_id = response.json()["id"] updated_flow = FlowCreate(name="Updated Flow", description="description", data=data) - response1 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.dict()) - response2 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.dict()) + response1 = client.put( + f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers + ) + response2 = client.put( + f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers + ) assert response1.json() == response2.json() -def test_update_nonexistent_flow(client: TestClient, json_flow: str): +def test_update_nonexistent_flow( + client: TestClient, json_flow: str, active_user, logged_in_headers +): flow_data = orjson.loads(json_flow) data = flow_data["data"] uuid = uuid4() @@ -223,17 +255,19 @@ def test_update_nonexistent_flow(client: TestClient, json_flow: str): description="description", data=data, ) - response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.dict()) + response = client.patch( + f"api/v1/flows/{uuid}", json=updated_flow.dict(), headers=logged_in_headers + ) assert response.status_code == 404 -def test_delete_nonexistent_flow(client: TestClient): +def test_delete_nonexistent_flow(client: TestClient, active_user, logged_in_headers): uuid = uuid4() - response = client.delete(f"api/v1/flows/{uuid}") + response = client.delete(f"api/v1/flows/{uuid}", headers=logged_in_headers) assert response.status_code == 404 -def test_read_empty_flows(client: TestClient): - response = client.get("api/v1/flows/") +def test_read_empty_flows(client: TestClient, active_user, logged_in_headers): + response = client.get("api/v1/flows/", headers=logged_in_headers) assert response.status_code == 200 assert len(response.json()) == 0 From dae856d0f6832016cc97656d58d03e6e585bd34a Mon Sep 17 00:00:00 2001 From: Guangya Liu Date: Thu, 24 Aug 2023 12:28:42 -0400 Subject: [PATCH 111/134] Added readme for docker example --- docker_example/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docker_example/README.md diff --git a/docker_example/README.md b/docker_example/README.md new file mode 100644 index 000000000..9e72dc645 --- /dev/null +++ b/docker_example/README.md @@ -0,0 +1,9 @@ +# LangFlow Docker Running + +```sh +git clone git@github.com:logspace-ai/langflow.git +cd langflow/docker_example +docker compose up +``` + +The web UI will be accessible on port [7860](http://localhost:7860/) \ No newline at end of file From 0a4c19082d9e94e340d8add4c8a995210c2d57a4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 11:16:08 -0300 Subject: [PATCH 112/134] =?UTF-8?q?=F0=9F=93=A6=20chore(pyproject.toml):?= =?UTF-8?q?=20add=20markupsafe=20dependency=20to=20improve=20compatibility?= =?UTF-8?q?=20and=20security=20=F0=9F=94=92=20chore(pyproject.toml):=20upd?= =?UTF-8?q?ate=20black=20dependency=20to=20version=2023.1.0=20for=20develo?= =?UTF-8?q?pment=20purposes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 14 +++++--------- pyproject.toml | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/poetry.lock b/poetry.lock index d775029c8..7580b7b71 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3022,9 +3022,9 @@ files = [ ] [package.dependencies] -diskcache = ">=5.6.1" -numpy = ">=1.20.0" -typing-extensions = ">=4.5.0" +diskcache = ">=5.6.1,<6.0.0" +numpy = ">=1.24.4,<2.0.0" +typing-extensions = ">=4.7.1,<5.0.0" [package.extras] server = ["fastapi (>=0.100.0)", "pydantic-settings (>=2.0.1)", "sse-starlette (>=1.6.1)", "uvicorn (>=0.23.2,<0.24.0)"] @@ -3057,8 +3057,6 @@ files = [ {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, @@ -3261,7 +3259,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." -optional = true +optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, @@ -4408,7 +4406,6 @@ files = [ {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, - {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, @@ -4418,7 +4415,6 @@ files = [ {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, - {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, @@ -7746,4 +7742,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "8ad605e7ea30f2819dbc03eac6c2e67576a98d1efa4890912414a7568fc27441" +content-hash = "fc078c55010bf3749e684cf032a4fc64b3918b15b60b4521c17a27815518032e" diff --git a/pyproject.toml b/pyproject.toml index 01e04fd5d..31e3d90ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,6 +82,7 @@ passlib = "^1.7.4" bcrypt = "^4.0.1" python-jose = "^3.3.0" metaphor-python = "^0.1.11" +markupsafe = "^2.1.3" [tool.poetry.group.dev.dependencies] black = "^23.1.0" From 82826d6b44b9158466fcb9290e4bcd8c871ac79b Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 25 Aug 2023 15:15:19 -0300 Subject: [PATCH 113/134] fix(constants.ts): update BASE_URL_API to "/api/v1/" to match backend API endpoint fix(API/index.ts): update API endpoints to use BASE_URL_API instead of hardcoding "/api/v1/" --- src/frontend/src/constants/constants.ts | 2 +- src/frontend/src/controllers/API/index.ts | 36 +++++++++++------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 5c8730f82..dee87dcdb 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -619,7 +619,7 @@ export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection."; export const FETCH_ERROR_DESCRIPION = "Check if everything is working properly and try again."; -export const BASE_URL_API = "http://localhost:7860/"; +export const BASE_URL_API = "/api/v1/"; export const SIGN_UP_SUCCESS = "Account created! Await admin activation. "; diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 796fbceb7..b49eb84ac 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -25,7 +25,7 @@ import { * @returns {Promise >} A promise that resolves to an AxiosResponse containing all the objects. */ export async function getAll(): Promise > { - return await api.get(`/api/v1/all`); + return await api.get(`${BASE_URL_API}all`); } const GITHUB_API_URL = "https://api.github.com"; @@ -47,13 +47,13 @@ export async function getRepoStars(owner: string, repo: string) { * @returns {AxiosResponse } The API response. */ export async function sendAll(data: sendAllProps) { - return await api.post(`/api/v1/predict`, data); + return await api.post(`${BASE_URL_API}predict`, data); } export async function postValidateCode( code: string ): Promise > { - return await api.post("/api/v1/validate/code", { code }); + return await api.post(`${BASE_URL_API}validate/code`, { code }); } /** @@ -68,7 +68,7 @@ export async function postValidatePrompt( template: string, frontend_node: APIClassType ): Promise > { - return await api.post("/api/v1/validate/prompt", { + return await api.post(`${BASE_URL_API}validate/prompt`, { name: name, template: template, frontend_node: frontend_node, @@ -112,7 +112,7 @@ export async function saveFlowToDatabase(newFlow: { style?: FlowStyleType; }): Promise { try { - const response = await api.post("/api/v1/flows/", { + const response = await api.post(`${BASE_URL_API}flows/`, { name: newFlow.name, data: newFlow.data, description: newFlow.description, @@ -138,7 +138,7 @@ export async function updateFlowInDatabase( updatedFlow: FlowType ): Promise { try { - const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, { + const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, { name: updatedFlow.name, data: updatedFlow.data, description: updatedFlow.description, @@ -162,7 +162,7 @@ export async function updateFlowInDatabase( */ export async function readFlowsFromDatabase() { try { - const response = await api.get("/api/v1/flows/"); + const response = await api.get(`${BASE_URL_API}flows/`); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -175,7 +175,7 @@ export async function readFlowsFromDatabase() { export async function downloadFlowsFromDatabase() { try { - const response = await api.get("/api/v1/flows/download/"); + const response = await api.get(`${BASE_URL_API}flows/download/`); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -188,7 +188,7 @@ export async function downloadFlowsFromDatabase() { export async function uploadFlowsToDatabase(flows: FormData) { try { - const response = await api.post(`/api/v1/flows/upload/`, flows); + const response = await api.post(`${BASE_URL_API}flows/upload/`, flows); if (response.status !== 201) { throw new Error(`HTTP error! status: ${response.status}`); @@ -209,7 +209,7 @@ export async function uploadFlowsToDatabase(flows: FormData) { */ export async function deleteFlowFromDatabase(flowId: string) { try { - const response = await api.delete(`/api/v1/flows/${flowId}`); + const response = await api.delete(`${BASE_URL_API}flows/${flowId}`); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -229,7 +229,7 @@ export async function deleteFlowFromDatabase(flowId: string) { */ export async function getFlowFromDatabase(flowId: number) { try { - const response = await api.get(`/api/v1/flows/${flowId}`); + const response = await api.get(`${BASE_URL_API}flows/${flowId}`); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -248,7 +248,7 @@ export async function getFlowFromDatabase(flowId: number) { */ export async function getFlowStylesFromDatabase() { try { - const response = await api.get("/api/v1/flow_styles/"); + const response = await api.get(`${BASE_URL_API}flow_styles/`); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -268,7 +268,7 @@ export async function getFlowStylesFromDatabase() { */ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) { try { - const response = await api.post("/api/v1/flow_styles/", flowStyle, { + const response = await api.post(`${BASE_URL_API}flow_styles/`, flowStyle, { headers: { accept: "application/json", "Content-Type": "application/json", @@ -291,7 +291,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) { * @returns {Promise >} A promise that resolves to an AxiosResponse containing the version information. */ export async function getVersion() { - const respnose = await api.get("/api/v1/version"); + const respnose = await api.get(`${BASE_URL_API}version`); return respnose.data; } @@ -313,7 +313,7 @@ export async function getHealth() { export async function getBuildStatus( flowId: string ): Promise { - return await api.get(`/api/v1/build/${flowId}/status`); + return await api.get(`${BASE_URL_API}build/${flowId}/status`); } //docs for postbuildinit @@ -326,7 +326,7 @@ export async function getBuildStatus( export async function postBuildInit( flow: FlowType ): Promise > { - return await api.post(`/api/v1/build/init/${flow.id}`, flow); + return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow); } // fetch(`/upload/${id}`, { @@ -344,14 +344,14 @@ export async function uploadFile( ): Promise > { const formData = new FormData(); formData.append("file", file); - return await api.post(`/api/v1/upload/${id}`, formData); + return await api.post(`${BASE_URL_API}upload/${id}`, formData); } export async function postCustomComponent( code: string, apiClass: APIClassType ): Promise > { - return await api.post(`/api/v1/custom_component`, { code }); + return await api.post(`${BASE_URL_API}custom_component`, { code }); } export async function onLogin(user: LoginType) { From c268b2be1728df1cfd1f9bcb62fc8aff5f3378cb Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:19:13 -0300 Subject: [PATCH 114/134] =?UTF-8?q?=F0=9F=94=A5=20refactor(models):=20remo?= =?UTF-8?q?ve=20Token=20model=20and=20references=20to=20it=20to=20simplify?= =?UTF-8?q?=20the=20codebase=20and=20remove=20unused=20code=20=F0=9F=94=A5?= =?UTF-8?q?=20refactor(test=5Fcustom=5Fcomponent.py):=20remove=20unused=20?= =?UTF-8?q?fixture=20and=20imports=20to=20clean=20up=20the=20test=20file?= =?UTF-8?q?=20=F0=9F=94=A7=20chore(test=5Fcustom=5Fcomponent.py):=20update?= =?UTF-8?q?=20component=20fixture=20to=20include=20user=5Fid=20parameter?= =?UTF-8?q?=20for=20testing=20purposes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/database/models/__init__.py | 3 +-- .../langflow/services/database/models/token/__init__.py | 5 ----- .../langflow/services/database/models/token/token.py | 7 ------- tests/test_custom_component.py | 5 +++-- 4 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 src/backend/langflow/services/database/models/token/__init__.py delete mode 100644 src/backend/langflow/services/database/models/token/token.py diff --git a/src/backend/langflow/services/database/models/__init__.py b/src/backend/langflow/services/database/models/__init__.py index 01e81e277..3cc4231a3 100644 --- a/src/backend/langflow/services/database/models/__init__.py +++ b/src/backend/langflow/services/database/models/__init__.py @@ -1,6 +1,5 @@ from .flow import Flow from .user import User -from .token import Token from .api_key import ApiKey -__all__ = ["Flow", "User", "Token", "ApiKey"] +__all__ = ["Flow", "User", "ApiKey"] diff --git a/src/backend/langflow/services/database/models/token/__init__.py b/src/backend/langflow/services/database/models/token/__init__.py deleted file mode 100644 index 9b9fa397d..000000000 --- a/src/backend/langflow/services/database/models/token/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .token import Token - -__all__ = [ - "Token", -] diff --git a/src/backend/langflow/services/database/models/token/token.py b/src/backend/langflow/services/database/models/token/token.py deleted file mode 100644 index 68c70f07f..000000000 --- a/src/backend/langflow/services/database/models/token/token.py +++ /dev/null @@ -1,7 +0,0 @@ -from pydantic import BaseModel - - -class Token(BaseModel): - access_token: str - refresh_token: str - token_type: str diff --git a/tests/test_custom_component.py b/tests/test_custom_component.py index 4dc8c9f1a..e75dc0e5b 100644 --- a/tests/test_custom_component.py +++ b/tests/test_custom_component.py @@ -473,15 +473,16 @@ def test_build_config_no_code(): @pytest.fixture -def component(): +def component(client, active_user): return CustomComponent( + user_id=active_user.id, field_config={ "fields": { "llm": {"type": "str"}, "url": {"type": "str"}, "year": {"type": "int"}, } - } + }, ) From 1a50bcd183a6a18572a1341d986d17d59639531d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:19:31 -0300 Subject: [PATCH 115/134] =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20change?= =?UTF-8?q?=20return=20type=20of=20generator=5Fbuild=20method=20to=20Gener?= =?UTF-8?q?ator[Vertex,=20None,=20None]=20for=20better=20type=20hinting=20?= =?UTF-8?q?and=20clarity=20=F0=9F=90=9B=20fix(base.py):=20add=20optional?= =?UTF-8?q?=20user=5Fid=20parameter=20to=20=5Fbuild=20and=20=5Fbuild=5Flis?= =?UTF-8?q?t=5Fof=5Fnodes=5Fand=5Fupdate=5Fparams=20methods=20in=20Vertex?= =?UTF-8?q?=20class=20to=20support=20building=20with=20user-specific=20dat?= =?UTF-8?q?a=20=F0=9F=90=9B=20fix(base.py):=20add=20optional=20user=5Fid?= =?UTF-8?q?=20parameter=20to=20=5Fget=5Fand=5Finstantiate=5Fclass=20method?= =?UTF-8?q?=20in=20Vertex=20class=20to=20support=20building=20with=20user-?= =?UTF-8?q?specific=20data=20=F0=9F=90=9B=20fix(custom=5Fcomponent.py):=20?= =?UTF-8?q?add=20user=5Fid=20attribute=20to=20CustomComponent=20class=20to?= =?UTF-8?q?=20store=20the=20user=20ID=20associated=20with=20the=20componen?= =?UTF-8?q?t=20=F0=9F=90=9B=20fix(custom=5Fcomponent.py):=20add=20user=5Fi?= =?UTF-8?q?d=20parameter=20to=20list=5Fflows=20method=20in=20CustomCompone?= =?UTF-8?q?nt=20class=20to=20filter=20flows=20by=20user=20ID=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(custom=5Fcomponent.py):=20add=20user=5Fid=20?= =?UTF-8?q?parameter=20to=20get=5Fflow=20method=20in=20CustomComponent=20c?= =?UTF-8?q?lass=20to=20filter=20flows=20by=20user=20ID=20=F0=9F=90=9B=20fi?= =?UTF-8?q?x(conftest.py):=20add=20client=20parameter=20to=20active=5Fuser?= =?UTF-8?q?=20fixture=20in=20tests=20to=20fix=20missing=20dependency=20err?= =?UTF-8?q?or?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/graph/graph/base.py | 2 +- src/backend/langflow/graph/vertex/base.py | 23 +++++++++-------- .../interface/custom/custom_component.py | 25 +++++++++++++------ tests/conftest.py | 2 +- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py index f0d3986cf..2b22d352c 100644 --- a/src/backend/langflow/graph/graph/base.py +++ b/src/backend/langflow/graph/graph/base.py @@ -144,7 +144,7 @@ class Graph: return list(reversed(sorted_vertices)) - def generator_build(self) -> Generator: + def generator_build(self) -> Generator[Vertex, None, None]: """Builds each vertex in the graph and yields it.""" sorted_vertices = self.topological_sort() logger.debug("Sorted vertices: %s", sorted_vertices) diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index 425f66315..ade10365b 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -133,13 +133,13 @@ class Vertex: # Add _type to params self.params = params - def _build(self): + def _build(self, user_id=None): """ Initiate the build process. """ logger.debug(f"Building {self.vertex_type}") - self._build_each_node_in_params_dict() - self._get_and_instantiate_class() + self._build_each_node_in_params_dict(user_id) + self._get_and_instantiate_class(user_id) self._validate_built_object() self._built = True @@ -169,23 +169,25 @@ class Vertex: """ return all(self._is_node(node) for node in value) - def _build_node_and_update_params(self, key, node): + def _build_node_and_update_params(self, key, node, user_id=None): """ Builds a given node and updates the params dictionary accordingly. """ - result = node.build() + result = node.build(user_id) self._handle_func(key, result) if isinstance(result, list): self._extend_params_list_with_result(key, result) self.params[key] = result - def _build_list_of_nodes_and_update_params(self, key, nodes): + def _build_list_of_nodes_and_update_params( + self, key, nodes: List["Vertex"], user_id=None + ): """ Iterates over a list of nodes, builds each and updates the params dictionary. """ self.params[key] = [] for node in nodes: - built = node.build() + built = node.build(user_id) if isinstance(built, list): if key not in self.params: self.params[key] = [] @@ -215,7 +217,7 @@ class Vertex: if isinstance(self.params[key], list): self.params[key].extend(result) - def _get_and_instantiate_class(self): + def _get_and_instantiate_class(self, user_id=None): """ Gets the class from a dictionary and instantiates it with the params. """ @@ -226,6 +228,7 @@ class Vertex: node_type=self.vertex_type, base_type=self.base_type, params=self.params, + user_id=user_id, ) self._update_built_object_and_artifacts(result) except Exception as exc: @@ -255,9 +258,9 @@ class Vertex: raise ValueError(message) - def build(self, force: bool = False) -> Any: + def build(self, force: bool = False, user_id=None) -> Any: if not self._built or force: - self._build() + self._build(user_id) return self._built_object diff --git a/src/backend/langflow/interface/custom/custom_component.py b/src/backend/langflow/interface/custom/custom_component.py index 88d2bcc82..1357daf68 100644 --- a/src/backend/langflow/interface/custom/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, List, Optional +from typing import Any, Callable, List, Optional, Union +from uuid import UUID from fastapi import HTTPException from langflow.interface.custom.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.custom.component import Component @@ -22,6 +23,7 @@ class CustomComponent(Component, extra=Extra.allow): function: Optional[Callable] = None return_type_valid_list = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys()) repr_value: Optional[Any] = "" + user_id: Optional[Union[UUID, str]] = None def __init__(self, **data): super().__init__(**data) @@ -187,11 +189,16 @@ class CustomComponent(Component, extra=Extra.allow): return build_sorted_vertices_with_caching(graph_data) def list_flows(self, *, get_session: Optional[Callable] = None) -> List[Flow]: - get_session = get_session or session_getter - db_manager = get_db_manager() - with get_session(db_manager) as session: - flows = session.query(Flow).all() - return flows + if not self.user_id: + raise ValueError("Session is invalid") + try: + get_session = get_session or session_getter + db_manager = get_db_manager() + with get_session(db_manager) as session: + flows = session.query(Flow).filter(Flow.user_id == self.user_id).all() + return flows + except Exception as e: + raise ValueError("Session is invalid") from e def get_flow( self, @@ -207,7 +214,11 @@ class CustomComponent(Component, extra=Extra.allow): if flow_id: flow = session.query(Flow).get(flow_id) elif flow_name: - flow = session.query(Flow).filter(Flow.name == flow_name).first() + flow = ( + session.query(Flow) + .filter(Flow.name == flow_name) + .filter(Flow.user_id == self.user_id) + ).first() else: raise ValueError("Either flow_name or flow_id must be provided") diff --git a/tests/conftest.py b/tests/conftest.py index 9abe89d49..1359664a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -170,7 +170,7 @@ def test_user(client): @pytest.fixture(scope="function") -def active_user(session): +def active_user(client, session): user = User( username="activeuser", password=get_password_hash( From d8f5efabad88dd3cae09762359342207f255a0f0 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:21:38 -0300 Subject: [PATCH 116/134] =?UTF-8?q?=F0=9F=90=9B=20fix(loading.py):=20add?= =?UTF-8?q?=20type=20hinting=20for=20TYPE=5FCHECKING=20to=20improve=20code?= =?UTF-8?q?=20readability=20=F0=9F=90=9B=20fix(loading.py):=20add=20option?= =?UTF-8?q?al=20user=5Fid=20parameter=20to=20instantiate=5Fclass=20and=20i?= =?UTF-8?q?nstantiate=5Fbased=5Fon=5Ftype=20functions=20to=20support=20use?= =?UTF-8?q?r-specific=20custom=20components=20=E2=9C=A8=20feat(loading.py)?= =?UTF-8?q?:=20modify=20instantiate=5Fcustom=5Fcomponent=20function=20to?= =?UTF-8?q?=20use=20user=5Fid=20parameter=20and=20improve=20code=20readabi?= =?UTF-8?q?lity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langflow/interface/initialize/loading.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/backend/langflow/interface/initialize/loading.py b/src/backend/langflow/interface/initialize/loading.py index 33e0d6b39..589d4b3ff 100644 --- a/src/backend/langflow/interface/initialize/loading.py +++ b/src/backend/langflow/interface/initialize/loading.py @@ -1,6 +1,6 @@ import json import orjson -from typing import Any, Callable, Dict, Sequence, Type +from typing import Any, Callable, Dict, Sequence, Type, TYPE_CHECKING from langchain.agents import agent as agent_module from langchain.agents.agent import AgentExecutor @@ -36,8 +36,13 @@ from langchain.vectorstores.base import VectorStore from langchain.document_loaders.base import BaseLoader from langflow.utils.logger import logger +if TYPE_CHECKING: + from langflow import CustomComponent -def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: + +def instantiate_class( + node_type: str, base_type: str, params: Dict, user_id=None +) -> Any: """Instantiate class from module type and key, and params""" params = convert_params_to_sets(params) params = convert_kwargs(params) @@ -48,7 +53,9 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: return custom_node(**params) logger.debug(f"Instantiating {node_type} of type {base_type}") class_object = import_by_type(_type=base_type, name=node_type) - return instantiate_based_on_type(class_object, base_type, node_type, params) + return instantiate_based_on_type( + class_object, base_type, node_type, params, user_id=user_id + ) def convert_params_to_sets(params): @@ -75,7 +82,7 @@ def convert_kwargs(params): return params -def instantiate_based_on_type(class_object, base_type, node_type, params): +def instantiate_based_on_type(class_object, base_type, node_type, params, user_id): if base_type == "agents": return instantiate_agent(node_type, class_object, params) elif base_type == "prompts": @@ -109,19 +116,19 @@ def instantiate_based_on_type(class_object, base_type, node_type, params): elif base_type == "memory": return instantiate_memory(node_type, class_object, params) elif base_type == "custom_components": - return instantiate_custom_component(node_type, class_object, params) + return instantiate_custom_component(node_type, class_object, params, user_id) elif base_type == "wrappers": return instantiate_wrapper(node_type, class_object, params) else: return class_object(**params) -def instantiate_custom_component(node_type, class_object, params): +def instantiate_custom_component(node_type, class_object, params, user_id): # we need to make a copy of the params because we will be # modifying it params_copy = params.copy() - class_object = get_function_custom(params_copy.pop("code")) - custom_component = class_object() + class_object: "CustomComponent" = get_function_custom(params_copy.pop("code")) + custom_component = class_object(user_id=user_id) built_object = custom_component.build(**params_copy) return built_object, {"repr": custom_component.custom_repr()} From 3d650c8491bf51fa497a238030fdc51f20b4c8f2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:21:52 -0300 Subject: [PATCH 117/134] =?UTF-8?q?=E2=9C=A8=20feat(schemas.py):=20add=20T?= =?UTF-8?q?oken=20schema=20to=20represent=20access=20and=20refresh=20token?= =?UTF-8?q?s=20in=20API=20responses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/schemas.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 47f58d830..c0867feb5 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -163,3 +163,9 @@ class ApiKeysResponse(BaseModel): class CreateApiKeyRequest(BaseModel): name: str + + +class Token(BaseModel): + access_token: str + refresh_token: str + token_type: str From b41b3038f324926c20823378f2964a5668e50149 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:23:37 -0300 Subject: [PATCH 118/134] =?UTF-8?q?=F0=9F=94=84=20refactor(login.py):=20up?= =?UTF-8?q?date=20import=20statement=20for=20Token=20model=20to=20match=20?= =?UTF-8?q?new=20location=20in=20schemas=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index a11167a40..fc6d0444d 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from langflow.services.utils import get_session -from langflow.services.database.models import Token +from langflow.api.v1.schemas import Token from langflow.services.auth.utils import ( authenticate_user, create_user_tokens, From 853ce351c9020075fdb99e428b96b28f7cc60802 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:24:45 -0300 Subject: [PATCH 119/134] =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20mi?= =?UTF-8?q?ssing=20import=20statement=20for=20get=5Fcurrent=5Factive=5Fuse?= =?UTF-8?q?r=20function=20=E2=9C=A8=20feat(chat.py):=20add=20current=5Fuse?= =?UTF-8?q?r=20dependency=20to=20chat=20and=20init=5Fbuild=20endpoints=20t?= =?UTF-8?q?o=20enforce=20authentication=20and=20authorization=20?= =?UTF-8?q?=F0=9F=94=A7=20refactor(chat.py):=20pass=20user=5Fid=20to=20ver?= =?UTF-8?q?tex.build()=20method=20in=20stream=5Fbuild=20endpoint=20to=20as?= =?UTF-8?q?sociate=20the=20build=20with=20the=20current=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/chat.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index f2b494803..459abcf51 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -1,10 +1,18 @@ -from fastapi import APIRouter, HTTPException, WebSocket, WebSocketException, status +from fastapi import ( + APIRouter, + Depends, + HTTPException, + WebSocket, + WebSocketException, + status, +) from fastapi.responses import StreamingResponse from langflow.api.utils import build_input_keys_response from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData from langflow.services import service_manager, ServiceType from langflow.graph.graph.base import Graph +from langflow.services.auth.utils import get_current_active_user from langflow.utils.logger import logger from cachetools import LRUCache @@ -14,7 +22,9 @@ flow_data_store: LRUCache = LRUCache(maxsize=10) @router.websocket("/chat/{client_id}") -async def chat(client_id: str, websocket: WebSocket): +async def chat( + client_id: str, websocket: WebSocket, current_user=Depends(get_current_active_user) +): """Websocket endpoint for chat.""" try: chat_manager = service_manager.get(ServiceType.CHAT_MANAGER) @@ -32,7 +42,9 @@ async def chat(client_id: str, websocket: WebSocket): @router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201) -async def init_build(graph_data: dict, flow_id: str): +async def init_build( + graph_data: dict, flow_id: str, current_user=Depends(get_current_active_user) +): """Initialize the build by storing graph data and returning a unique session ID.""" try: @@ -54,6 +66,7 @@ async def init_build(graph_data: dict, flow_id: str): flow_data_store[flow_id] = { "graph_data": graph_data, "status": BuildStatus.STARTED, + "user_id": current_user.id, } return InitResponse(flowId=flow_id) @@ -99,6 +112,7 @@ async def stream_build(flow_id: str): return graph_data = flow_data_store[flow_id].get("graph_data") + user_id = flow_data_store[flow_id]["user_id"] if not graph_data: error_message = "No data provided" @@ -119,7 +133,7 @@ async def stream_build(flow_id: str): "log": f"Building node {vertex.vertex_type}", } yield str(StreamData(event="log", data=log_dict)) - vertex.build() + vertex.build(user_id) params = vertex._built_object_repr() valid = True logger.debug(f"Building node {str(vertex.vertex_type)}") From 706cdc08747181c0bdc426a6fbdd715eb934cf6b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:25:09 -0300 Subject: [PATCH 120/134] =?UTF-8?q?=F0=9F=9A=80=20feat(alembic):=20add=20m?= =?UTF-8?q?igration=20script=20to=20update=20all=20tables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a new migration script to update all tables in the database. The script includes the following changes: - Added columns 'created_at' and 'user_id' to the 'apikey' table - Altered the 'name' column in the 'apikey' table to allow null values - Created indexes on the 'name' and 'user_id' columns in the 'apikey' table - Created a foreign key constraint between the 'apikey' table and the 'user' table - Added the 'user_id' column to the 'flow' table - Created an index on the 'user_id' column in the 'flow' table - Created a foreign key constraint between the 'flow' table and the 'user' table - Removed the 'create_at' column from the 'apikey' table This migration script is designed to be used with Alembic for database schema updates. --- .../d3749cf7ac7e_update_all_tables.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py diff --git a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py new file mode 100644 index 000000000..fee7b9b93 --- /dev/null +++ b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py @@ -0,0 +1,53 @@ +"""Update all tables + +Revision ID: d3749cf7ac7e +Revises: 5512e39b4012 +Create Date: 2023-08-25 15:16:00.970071 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = 'd3749cf7ac7e' +down_revision: Union[str, None] = '5512e39b4012' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('apikey', sa.Column('created_at', sa.DateTime(), nullable=False)) + op.add_column('apikey', sa.Column('user_id', sqlmodel.sql.sqltypes.GUID(), nullable=False)) + op.alter_column('apikey', 'name', + existing_type=sa.VARCHAR(), + nullable=True) + op.create_index(op.f('ix_apikey_name'), 'apikey', ['name'], unique=False) + op.create_index(op.f('ix_apikey_user_id'), 'apikey', ['user_id'], unique=False) + op.create_foreign_key(None, 'apikey', 'user', ['user_id'], ['id']) + op.drop_column('apikey', 'create_at') + op.add_column('flow', sa.Column('user_id', sqlmodel.sql.sqltypes.GUID(), nullable=False)) + op.create_index(op.f('ix_flow_user_id'), 'flow', ['user_id'], unique=False) + op.create_foreign_key(None, 'flow', 'user', ['user_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'flow', type_='foreignkey') + op.drop_index(op.f('ix_flow_user_id'), table_name='flow') + op.drop_column('flow', 'user_id') + op.add_column('apikey', sa.Column('create_at', sa.DATETIME(), nullable=False)) + op.drop_constraint(None, 'apikey', type_='foreignkey') + op.drop_index(op.f('ix_apikey_user_id'), table_name='apikey') + op.drop_index(op.f('ix_apikey_name'), table_name='apikey') + op.alter_column('apikey', 'name', + existing_type=sa.VARCHAR(), + nullable=False) + op.drop_column('apikey', 'user_id') + op.drop_column('apikey', 'created_at') + # ### end Alembic commands ### From 8b99cc06c4c9d91603326056ba4259660e0acd7b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:28:49 -0300 Subject: [PATCH 121/134] =?UTF-8?q?=F0=9F=94=A7=20fix(update=5Fall=5Ftable?= =?UTF-8?q?s.py):=20handle=20potential=20OperationalError=20when=20adding?= =?UTF-8?q?=20columns=20to=20'apikey'=20table=20=E2=9C=A8=20feat(update=5F?= =?UTF-8?q?all=5Ftables.py):=20add=20support=20for=20'user=5Fid'=20column?= =?UTF-8?q?=20in=20'apikey'=20table=20=E2=9C=A8=20feat(update=5Fall=5Ftabl?= =?UTF-8?q?es.py):=20add=20support=20for=20'created=5Fat'=20column=20in=20?= =?UTF-8?q?'apikey'=20table=20=E2=9C=A8=20feat(update=5Fall=5Ftables.py):?= =?UTF-8?q?=20add=20support=20for=20index=20on=20'name'=20column=20in=20'a?= =?UTF-8?q?pikey'=20table=20=E2=9C=A8=20feat(update=5Fall=5Ftables.py):=20?= =?UTF-8?q?add=20support=20for=20index=20on=20'user=5Fid'=20column=20in=20?= =?UTF-8?q?'apikey'=20table=20=E2=9C=A8=20feat(update=5Fall=5Ftables.py):?= =?UTF-8?q?=20add=20foreign=20key=20constraint=20on=20'user=5Fid'=20column?= =?UTF-8?q?=20in=20'apikey'=20table=20=F0=9F=94=A7=20fix(update=5Fall=5Fta?= =?UTF-8?q?bles.py):=20handle=20potential=20OperationalError=20when=20drop?= =?UTF-8?q?ping=20'create=5Fat'=20column=20from=20'apikey'=20table=20?= =?UTF-8?q?=E2=9C=A8=20feat(update=5Fall=5Ftables.py):=20add=20support=20f?= =?UTF-8?q?or=20'user=5Fid'=20column=20in=20'flow'=20table=20=E2=9C=A8=20f?= =?UTF-8?q?eat(update=5Fall=5Ftables.py):=20add=20support=20for=20index=20?= =?UTF-8?q?on=20'user=5Fid'=20column=20in=20'flow'=20table=20=E2=9C=A8=20f?= =?UTF-8?q?eat(update=5Fall=5Ftables.py):=20add=20foreign=20key=20constrai?= =?UTF-8?q?nt=20on=20'user=5Fid'=20column=20in=20'flow'=20table=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(update=5Fall=5Ftables.py):=20handle=20potent?= =?UTF-8?q?ial=20OperationalError=20when=20dropping=20foreign=20key=20cons?= =?UTF-8?q?traint=20from=20'flow'=20table=20=F0=9F=94=A7=20fix(update=5Fal?= =?UTF-8?q?l=5Ftables.py):=20handle=20potential=20OperationalError=20when?= =?UTF-8?q?=20dropping=20index=20on=20'user=5Fid'=20column=20from=20'flow'?= =?UTF-8?q?=20table=20=F0=9F=94=A7=20fix(update=5Fall=5Ftables.py):=20hand?= =?UTF-8?q?le=20potential=20OperationalError=20when=20dropping=20'user=5Fi?= =?UTF-8?q?d'=20column=20from=20'flow'=20table=20=F0=9F=94=A7=20fix(update?= =?UTF-8?q?=5Fall=5Ftables.py):=20handle=20potential=20OperationalError=20?= =?UTF-8?q?when=20adding=20'create=5Fat'=20column=20to=20'apikey'=20table?= =?UTF-8?q?=20=F0=9F=94=A7=20fix(update=5Fall=5Ftables.py):=20handle=20pot?= =?UTF-8?q?ential=20OperationalError=20when=20dropping=20foreign=20key=20c?= =?UTF-8?q?onstraint=20from=20'apikey'=20table=20=F0=9F=94=A7=20fix(update?= =?UTF-8?q?=5Fall=5Ftables.py):=20handle=20potential=20OperationalError=20?= =?UTF-8?q?when=20dropping=20index=20on=20'user=5Fid'=20column=20from=20'a?= =?UTF-8?q?pikey'=20table=20=F0=9F=94=A7=20fix(update=5Fall=5Ftables.py):?= =?UTF-8?q?=20handle=20potential=20OperationalError=20when=20dropping=20in?= =?UTF-8?q?dex=20on=20'name'=20column=20from=20'apikey'=20table=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(update=5Fall=5Ftables.py):=20handle=20potent?= =?UTF-8?q?ial=20OperationalError=20when=20altering=20'name'=20column=20in?= =?UTF-8?q?=20'apikey'=20table=20=F0=9F=94=A7=20fix(update=5Fall=5Ftables.?= =?UTF-8?q?py):=20handle=20potential=20OperationalError=20when=20dropping?= =?UTF-8?q?=20'user=5Fid'=20column=20from=20'apikey'=20table=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(update=5Fall=5Ftables.py):=20handle=20potent?= =?UTF-8?q?ial=20OperationalError=20when=20dropping=20'created=5Fat'=20col?= =?UTF-8?q?umn=20from=20'apikey'=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../d3749cf7ac7e_update_all_tables.py | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py index fee7b9b93..13c0266e7 100644 --- a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py +++ b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py @@ -10,44 +10,49 @@ from typing import Sequence, Union from alembic import op import sqlalchemy as sa import sqlmodel - +import contextlib # revision identifiers, used by Alembic. -revision: str = 'd3749cf7ac7e' -down_revision: Union[str, None] = '5512e39b4012' +revision: str = "d3749cf7ac7e" +down_revision: Union[str, None] = "5512e39b4012" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.add_column('apikey', sa.Column('created_at', sa.DateTime(), nullable=False)) - op.add_column('apikey', sa.Column('user_id', sqlmodel.sql.sqltypes.GUID(), nullable=False)) - op.alter_column('apikey', 'name', - existing_type=sa.VARCHAR(), - nullable=True) - op.create_index(op.f('ix_apikey_name'), 'apikey', ['name'], unique=False) - op.create_index(op.f('ix_apikey_user_id'), 'apikey', ['user_id'], unique=False) - op.create_foreign_key(None, 'apikey', 'user', ['user_id'], ['id']) - op.drop_column('apikey', 'create_at') - op.add_column('flow', sa.Column('user_id', sqlmodel.sql.sqltypes.GUID(), nullable=False)) - op.create_index(op.f('ix_flow_user_id'), 'flow', ['user_id'], unique=False) - op.create_foreign_key(None, 'flow', 'user', ['user_id'], ['id']) + with contextlib.suppress(sa.exc.OperationalError): + op.add_column("apikey", sa.Column("created_at", sa.DateTime(), nullable=False)) + with contextlib.suppress(sa.exc.OperationalError): + op.add_column( + "apikey", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) + ) + with contextlib.suppress(sa.exc.OperationalError): + op.alter_column("apikey", "name", existing_type=sa.VARCHAR(), nullable=True) + with contextlib.suppress(sa.exc.OperationalError): + op.create_index(op.f("ix_apikey_name"), "apikey", ["name"], unique=False) + op.create_index(op.f("ix_apikey_user_id"), "apikey", ["user_id"], unique=False) + op.create_foreign_key(None, "apikey", "user", ["user_id"], ["id"]) + with contextlib.suppress(sa.exc.OperationalError): + op.drop_column("apikey", "create_at") + op.add_column( + "flow", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) + ) + op.create_index(op.f("ix_flow_user_id"), "flow", ["user_id"], unique=False) + op.create_foreign_key(None, "flow", "user", ["user_id"], ["id"]) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'flow', type_='foreignkey') - op.drop_index(op.f('ix_flow_user_id'), table_name='flow') - op.drop_column('flow', 'user_id') - op.add_column('apikey', sa.Column('create_at', sa.DATETIME(), nullable=False)) - op.drop_constraint(None, 'apikey', type_='foreignkey') - op.drop_index(op.f('ix_apikey_user_id'), table_name='apikey') - op.drop_index(op.f('ix_apikey_name'), table_name='apikey') - op.alter_column('apikey', 'name', - existing_type=sa.VARCHAR(), - nullable=False) - op.drop_column('apikey', 'user_id') - op.drop_column('apikey', 'created_at') + op.drop_constraint(None, "flow", type_="foreignkey") + op.drop_index(op.f("ix_flow_user_id"), table_name="flow") + op.drop_column("flow", "user_id") + op.add_column("apikey", sa.Column("create_at", sa.DATETIME(), nullable=False)) + op.drop_constraint(None, "apikey", type_="foreignkey") + op.drop_index(op.f("ix_apikey_user_id"), table_name="apikey") + op.drop_index(op.f("ix_apikey_name"), table_name="apikey") + op.alter_column("apikey", "name", existing_type=sa.VARCHAR(), nullable=False) + op.drop_column("apikey", "user_id") + op.drop_column("apikey", "created_at") # ### end Alembic commands ### From 648bb25b3f6dc7a11138f487f280360ad389a924 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 15:31:16 -0300 Subject: [PATCH 122/134] =?UTF-8?q?=F0=9F=94=A7=20chore(update=5Fall=5Ftab?= =?UTF-8?q?les.py):=20add=20missing=20index=20and=20foreign=20key=20constr?= =?UTF-8?q?aints=20to=20apikey=20table=20=F0=9F=94=A7=20chore(update=5Fall?= =?UTF-8?q?=5Ftables.py):=20add=20missing=20index=20and=20foreign=20key=20?= =?UTF-8?q?constraints=20to=20flow=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../versions/d3749cf7ac7e_update_all_tables.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py index 13c0266e7..f45034d40 100644 --- a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py +++ b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py @@ -27,19 +27,21 @@ def upgrade() -> None: op.add_column( "apikey", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) ) + op.create_index(op.f("ix_apikey_user_id"), "apikey", ["user_id"], unique=False) + op.create_foreign_key(None, "apikey", "user", ["user_id"], ["id"]) with contextlib.suppress(sa.exc.OperationalError): op.alter_column("apikey", "name", existing_type=sa.VARCHAR(), nullable=True) - with contextlib.suppress(sa.exc.OperationalError): op.create_index(op.f("ix_apikey_name"), "apikey", ["name"], unique=False) - op.create_index(op.f("ix_apikey_user_id"), "apikey", ["user_id"], unique=False) - op.create_foreign_key(None, "apikey", "user", ["user_id"], ["id"]) + with contextlib.suppress(sa.exc.OperationalError): op.drop_column("apikey", "create_at") - op.add_column( - "flow", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) - ) - op.create_index(op.f("ix_flow_user_id"), "flow", ["user_id"], unique=False) - op.create_foreign_key(None, "flow", "user", ["user_id"], ["id"]) + with contextlib.suppress(sa.exc.OperationalError): + op.add_column( + "flow", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) + ) + with contextlib.suppress(sa.exc.OperationalError): + op.create_index(op.f("ix_flow_user_id"), "flow", ["user_id"], unique=False) + op.create_foreign_key(None, "flow", "user", ["user_id"], ["id"]) # ### end Alembic commands ### From 2af4508e5e51f4de542b295bb3e9d41143aa37be Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:28:56 -0300 Subject: [PATCH 123/134] =?UTF-8?q?=E2=9C=A8=20feat(main.py):=20add=20heal?= =?UTF-8?q?th=20endpoint=20to=20check=20the=20status=20of=20the=20applicat?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index a383a2afa..d0d62a804 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -31,6 +31,10 @@ def create_app(): allow_headers=["*"], ) + @app.get("/health") + def health(): + return {"status": "ok"} + app.include_router(router) app.on_event("startup")(initialize_services) From 132642347f41d981f1c3493b8332f9e57c53d624 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:29:15 -0300 Subject: [PATCH 124/134] =?UTF-8?q?=F0=9F=94=A7=20fix(endpoints.py):=20add?= =?UTF-8?q?=20current=5Fuser=20dependency=20to=20get=5Fall=20endpoint=20to?= =?UTF-8?q?=20enforce=20authentication=20and=20authorization=20?= =?UTF-8?q?=F0=9F=9A=80=20feat(endpoints.py):=20import=20User=20model=20an?= =?UTF-8?q?d=20get=5Fcurrent=5Factive=5Fuser=20function=20to=20enable=20au?= =?UTF-8?q?thentication=20and=20authorization=20in=20get=5Fall=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/endpoints.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 46acd4683..088c00b13 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -1,9 +1,11 @@ from http import HTTPStatus from typing import Annotated, Optional, Union +from langflow.services.auth.utils import get_current_active_user from langflow.services.cache.utils import save_uploaded_file from langflow.services.database.models.flow import Flow from langflow.processing.process import process_graph_cached, process_tweaks +from langflow.services.database.models.user.user import User from langflow.services.utils import get_settings_manager from langflow.utils.logger import logger from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body @@ -33,7 +35,7 @@ router = APIRouter(tags=["Base"]) @router.get("/all") -def get_all(): +def get_all(current_user: User = Depends(get_current_active_user)): logger.debug("Building langchain types dict") native_components = build_langchain_types_dict() # custom_components is a list of dicts From 89ea8e45f6e0051a90cb3db67cb09f486a93eaae Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:29:24 -0300 Subject: [PATCH 125/134] =?UTF-8?q?=F0=9F=94=A7=20chore(login.py):=20impor?= =?UTF-8?q?t=20get=5Fcurrent=5Factive=5Fuser=20function=20to=20improve=20c?= =?UTF-8?q?ode=20readability=20and=20maintainability=20=E2=9C=A8=20feat(lo?= =?UTF-8?q?gin.py):=20add=20current=5Fuser=20dependency=20to=20refresh=5Ft?= =?UTF-8?q?oken=20endpoint=20to=20ensure=20only=20authenticated=20users=20?= =?UTF-8?q?can=20refresh=20their=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/login.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index fc6d0444d..afe67a916 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -9,6 +9,7 @@ from langflow.services.auth.utils import ( create_user_tokens, create_refresh_token, create_user_longterm_token, + get_current_active_user, ) from langflow.services.utils import get_settings_manager @@ -49,7 +50,9 @@ async def auto_login(db: Session = Depends(get_session)): @router.post("/refresh") -async def refresh_token(token: str): +async def refresh_token( + token: str, current_user: Session = Depends(get_current_active_user) +): if token: return create_refresh_token(token) else: From e2122567bed9cb2ffe920e1aa5be9e68772f22d6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:29:34 -0300 Subject: [PATCH 126/134] =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20remove?= =?UTF-8?q?=20unnecessary=20user=5Fid=20parameter=20from=20=5Fbuild=5Feach?= =?UTF-8?q?=5Fnode=5Fin=5Fparams=5Fdict()=20method=20call=20in=20Vertex=20?= =?UTF-8?q?class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/graph/vertex/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index ade10365b..09c813a2e 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -138,7 +138,7 @@ class Vertex: Initiate the build process. """ logger.debug(f"Building {self.vertex_type}") - self._build_each_node_in_params_dict(user_id) + self._build_each_node_in_params_dict() self._get_and_instantiate_class(user_id) self._validate_built_object() From aa633ff2ded39bcc94b75ccfeb34e973a60cddd1 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:29:43 -0300 Subject: [PATCH 127/134] =?UTF-8?q?=F0=9F=90=9B=20fix(utils.py):=20change?= =?UTF-8?q?=20auth=5Fscheme=5Fdependency=20function=20to=20be=20async=20to?= =?UTF-8?q?=20match=20its=20usage=20in=20get=5Fcurrent=5Fuser=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/auth/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index 8cc67d216..3e9f0f582 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -14,12 +14,12 @@ from langflow.services.utils import get_session, get_settings_manager from sqlmodel import Session -def auth_scheme_dependency(request: Request): +async def auth_scheme_dependency(request: Request): settings_manager = ( get_settings_manager() ) # Assuming get_settings_manager is defined - return AuthManager(settings_manager).run_oauth2_scheme(request) + return await AuthManager(settings_manager).run_oauth2_scheme(request) async def get_current_user( From 1364fa0e0f7b70b6f06036e547ae3e14d5d49092 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:29:51 -0300 Subject: [PATCH 128/134] =?UTF-8?q?=F0=9F=94=A7=20chore(conftest.py):=20ch?= =?UTF-8?q?ange=20scope=20of=20client=20fixture=20to=20module=20and=20add?= =?UTF-8?q?=20autouse=3DTrue=20to=20ensure=20it=20is=20automatically=20use?= =?UTF-8?q?d=20by=20all=20tests=20in=20the=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1359664a0..d0829d0c8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,7 +45,7 @@ async def async_client() -> AsyncGenerator: # Create client fixture for FastAPI -@pytest.fixture(scope="module") +@pytest.fixture(scope="module", autouse=True) def client(): from langflow.main import create_app From 812864eded52304ab4d19734e17ce63d9d338d17 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:30:01 -0300 Subject: [PATCH 129/134] =?UTF-8?q?=F0=9F=94=A7=20fix(test=5Fagents=5Ftemp?= =?UTF-8?q?late.py):=20update=20test=20functions=20to=20include=20logged?= =?UTF-8?q?=5Fin=5Fheaders=20parameter=20to=20test=20authenticated=20reque?= =?UTF-8?q?sts=20=F0=9F=94=A7=20fix(test=5Fchains=5Ftemplate.py):=20update?= =?UTF-8?q?=20test=20functions=20to=20include=20logged=5Fin=5Fheaders=20pa?= =?UTF-8?q?rameter=20to=20test=20authenticated=20requests=20=F0=9F=94=A7?= =?UTF-8?q?=20fix(test=5Fendpoints.py):=20update=20test=5Fget=5Fall=20func?= =?UTF-8?q?tion=20to=20include=20logged=5Fin=5Fheaders=20parameter=20to=20?= =?UTF-8?q?test=20authenticated=20requests=20=F0=9F=94=A7=20fix(test=5Fllm?= =?UTF-8?q?s=5Ftemplate.py):=20update=20test=20functions=20to=20include=20?= =?UTF-8?q?logged=5Fin=5Fheaders=20parameter=20to=20test=20authenticated?= =?UTF-8?q?=20requests=20=F0=9F=94=A7=20fix(test=5Fprompts=5Ftemplate.py):?= =?UTF-8?q?=20update=20test=20functions=20to=20include=20logged=5Fin=5Fhea?= =?UTF-8?q?ders=20parameter=20to=20test=20authenticated=20requests=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(test=5Fvectorstore=5Ftemplate.py):=20update?= =?UTF-8?q?=20test=20functions=20to=20include=20logged=5Fin=5Fheaders=20pa?= =?UTF-8?q?rameter=20to=20test=20authenticated=20requests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_agents_template.py | 16 +++++++-------- tests/test_chains_template.py | 32 +++++++++++++++--------------- tests/test_endpoints.py | 4 ++-- tests/test_llms_template.py | 12 +++++------ tests/test_prompts_template.py | 8 ++++---- tests/test_vectorstore_template.py | 4 ++-- 6 files changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/test_agents_template.py b/tests/test_agents_template.py index 0b5fb7c3a..b12ad7dee 100644 --- a/tests/test_agents_template.py +++ b/tests/test_agents_template.py @@ -1,8 +1,8 @@ from fastapi.testclient import TestClient -def test_zero_shot_agent(client: TestClient): - response = client.get("api/v1/all") +def test_zero_shot_agent(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() agents = json_response["agents"] @@ -113,8 +113,8 @@ def test_zero_shot_agent(client: TestClient): } -def test_json_agent(client: TestClient): - response = client.get("api/v1/all") +def test_json_agent(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() agents = json_response["agents"] @@ -152,8 +152,8 @@ def test_json_agent(client: TestClient): } -def test_csv_agent(client: TestClient): - response = client.get("api/v1/all") +def test_csv_agent(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() agents = json_response["agents"] @@ -195,8 +195,8 @@ def test_csv_agent(client: TestClient): } -def test_initialize_agent(client: TestClient): - response = client.get("api/v1/all") +def test_initialize_agent(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() agents = json_response["agents"] diff --git a/tests/test_chains_template.py b/tests/test_chains_template.py index 4339dbe3b..eb20a0571 100644 --- a/tests/test_chains_template.py +++ b/tests/test_chains_template.py @@ -1,8 +1,8 @@ from fastapi.testclient import TestClient -# def test_chains_settings(client: TestClient): -# response = client.get("api/v1/all") +# def test_chains_settings(client: TestClient, logged_in_headers): +# response = client.get("api/v1/all", headers=logged_in_headers) # assert response.status_code == 200 # json_response = response.json() # chains = json_response["chains"] @@ -10,8 +10,8 @@ from fastapi.testclient import TestClient # Test the ConversationChain object -def test_conversation_chain(client: TestClient): - response = client.get("api/v1/all") +def test_conversation_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] @@ -102,8 +102,8 @@ def test_conversation_chain(client: TestClient): ) -def test_llm_chain(client: TestClient): - response = client.get("api/v1/all") +def test_llm_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] @@ -173,8 +173,8 @@ def test_llm_chain(client: TestClient): } -def test_llm_checker_chain(client: TestClient): - response = client.get("api/v1/all") +def test_llm_checker_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] @@ -207,8 +207,8 @@ def test_llm_checker_chain(client: TestClient): assert chain["description"] == "" -def test_llm_math_chain(client: TestClient): - response = client.get("api/v1/all") +def test_llm_math_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] @@ -299,8 +299,8 @@ def test_llm_math_chain(client: TestClient): ) -def test_series_character_chain(client: TestClient): - response = client.get("api/v1/all") +def test_series_character_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] @@ -367,8 +367,8 @@ def test_series_character_chain(client: TestClient): ) -def test_mid_journey_prompt_chain(client: TestClient): - response = client.get("api/v1/all") +def test_mid_journey_prompt_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] @@ -408,8 +408,8 @@ def test_mid_journey_prompt_chain(client: TestClient): ) -def test_time_travel_guide_chain(client: TestClient): - response = client.get("api/v1/all") +def test_time_travel_guide_chain(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() chains = json_response["chains"] diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 045af1ba5..431999a0c 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -83,8 +83,8 @@ PROMPT_REQUEST = { } -def test_get_all(client: TestClient): - response = client.get("api/v1/all") +def test_get_all(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() # We need to test the custom nodes diff --git a/tests/test_llms_template.py b/tests/test_llms_template.py index f1b76e18e..c082baded 100644 --- a/tests/test_llms_template.py +++ b/tests/test_llms_template.py @@ -2,9 +2,9 @@ from fastapi.testclient import TestClient from langflow.services.utils import get_settings_manager -def test_llms_settings(client: TestClient): +def test_llms_settings(client: TestClient, logged_in_headers): settings_manager = get_settings_manager() - response = client.get("api/v1/all") + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() llms = json_response["llms"] @@ -103,8 +103,8 @@ def test_llms_settings(client: TestClient): # } -def test_openai(client: TestClient): - response = client.get("api/v1/all") +def test_openai(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() language_models = json_response["llms"] @@ -369,8 +369,8 @@ def test_openai(client: TestClient): } -def test_chat_open_ai(client: TestClient): - response = client.get("api/v1/all") +def test_chat_open_ai(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() language_models = json_response["llms"] diff --git a/tests/test_prompts_template.py b/tests/test_prompts_template.py index dde313c20..676448f73 100644 --- a/tests/test_prompts_template.py +++ b/tests/test_prompts_template.py @@ -2,17 +2,17 @@ from fastapi.testclient import TestClient from langflow.services.utils import get_settings_manager -def test_prompts_settings(client: TestClient): +def test_prompts_settings(client: TestClient, logged_in_headers): settings_manager = get_settings_manager() - response = client.get("api/v1/all") + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() prompts = json_response["prompts"] assert set(prompts.keys()) == set(settings_manager.settings.PROMPTS) -def test_prompt_template(client: TestClient): - response = client.get("api/v1/all") +def test_prompt_template(client: TestClient, logged_in_headers): + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() prompts = json_response["prompts"] diff --git a/tests/test_vectorstore_template.py b/tests/test_vectorstore_template.py index 4baa7f4b6..87394b890 100644 --- a/tests/test_vectorstore_template.py +++ b/tests/test_vectorstore_template.py @@ -4,9 +4,9 @@ from langflow.services.utils import get_settings_manager # check that all agents are in settings.agents # are in json_response["agents"] -def test_vectorstores_settings(client: TestClient): +def test_vectorstores_settings(client: TestClient, logged_in_headers): settings_manager = get_settings_manager() - response = client.get("api/v1/all") + response = client.get("api/v1/all", headers=logged_in_headers) assert response.status_code == 200 json_response = response.json() vectorstores = json_response["vectorstores"] From a290a6860553bce9b7ca28a94a07c6d32705a0dd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:30:16 -0300 Subject: [PATCH 130/134] =?UTF-8?q?=F0=9F=94=A7=20chore(update=5Fall=5Ftab?= =?UTF-8?q?les.py):=20remove=20unused=20code=20block=20to=20improve=20code?= =?UTF-8?q?=20readability=20and=20maintainability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py index f45034d40..e8f59089c 100644 --- a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py +++ b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py @@ -39,7 +39,6 @@ def upgrade() -> None: op.add_column( "flow", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) ) - with contextlib.suppress(sa.exc.OperationalError): op.create_index(op.f("ix_flow_user_id"), "flow", ["user_id"], unique=False) op.create_foreign_key(None, "flow", "user", ["user_id"], ["id"]) # ### end Alembic commands ### From fc32ee63e17258bfa25119c7faadcc08a9045b47 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 16:31:24 -0300 Subject: [PATCH 131/134] =?UTF-8?q?=F0=9F=90=9B=20fix(alembic):=20change?= =?UTF-8?q?=20"user=5Fid"=20column=20in=20"flow"=20table=20to=20be=20nulla?= =?UTF-8?q?ble=20to=20handle=20existing=20data=20without=20a=20user=5Fid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py index e8f59089c..27664d8b7 100644 --- a/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py +++ b/src/backend/langflow/alembic/versions/d3749cf7ac7e_update_all_tables.py @@ -37,7 +37,7 @@ def upgrade() -> None: op.drop_column("apikey", "create_at") with contextlib.suppress(sa.exc.OperationalError): op.add_column( - "flow", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=False) + "flow", sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=True) ) op.create_index(op.f("ix_flow_user_id"), "flow", ["user_id"], unique=False) op.create_foreign_key(None, "flow", "user", ["user_id"], ["id"]) From 4e1710bcc7d0ba766e3ce558928e8ab7c0edd0a7 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 25 Aug 2023 17:01:32 -0300 Subject: [PATCH 132/134] =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20mi?= =?UTF-8?q?ssing=20import=20statement=20for=20Query=20from=20fastapi=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20stateme?= =?UTF-8?q?nt=20for=20Session=20from=20sqlmodel=20=F0=9F=90=9B=20fix(chat.?= =?UTF-8?q?py):=20add=20missing=20import=20statement=20for=20get=5Fsession?= =?UTF-8?q?=20from=20langflow.services.utils=20=F0=9F=90=9B=20fix(chat.py)?= =?UTF-8?q?:=20add=20missing=20import=20statement=20for=20get=5Fcurrent=5F?= =?UTF-8?q?user=20from=20langflow.services.auth.utils=20=F0=9F=90=9B=20fix?= =?UTF-8?q?(chat.py):=20add=20missing=20import=20statement=20for=20HTTPExc?= =?UTF-8?q?eption=20from=20fastapi=20=F0=9F=90=9B=20fix(chat.py):=20add=20?= =?UTF-8?q?missing=20import=20statement=20for=20get=5Fcurrent=5Factive=5Fu?= =?UTF-8?q?ser=20from=20langflow.services.auth.utils=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?chat.py):=20add=20missing=20import=20statement=20for=20WebSocke?= =?UTF-8?q?t=20from=20fastapi=20=F0=9F=90=9B=20fix(chat.py):=20add=20missi?= =?UTF-8?q?ng=20import=20statement=20for=20WebSocketException=20from=20fas?= =?UTF-8?q?tapi=20=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20?= =?UTF-8?q?statement=20for=20status=20from=20fastapi=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?chat.py):=20add=20missing=20import=20statement=20for=20APIRoute?= =?UTF-8?q?r=20from=20fastapi=20=F0=9F=90=9B=20fix(chat.py):=20add=20missi?= =?UTF-8?q?ng=20import=20statement=20for=20Depends=20from=20fastapi=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20stateme?= =?UTF-8?q?nt=20for=20HTTPException=20from=20fastapi=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?chat.py):=20add=20missing=20import=20statement=20for=20get=5Fcu?= =?UTF-8?q?rrent=5Factive=5Fuser=20from=20langflow.services.auth.utils=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20stateme?= =?UTF-8?q?nt=20for=20get=5Fcurrent=5Fuser=20from=20langflow.services.auth?= =?UTF-8?q?.utils=20=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import?= =?UTF-8?q?=20statement=20for=20get=5Fsession=20from=20langflow.services.u?= =?UTF-8?q?tils=20=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20?= =?UTF-8?q?statement=20for=20Session=20from=20sqlmodel=20=F0=9F=90=9B=20fi?= =?UTF-8?q?x(chat.py):=20add=20missing=20import=20statement=20for=20HTTPEx?= =?UTF-8?q?ception=20from=20fastapi=20=F0=9F=90=9B=20fix(chat.py):=20add?= =?UTF-8?q?=20missing=20import=20statement=20for=20get=5Fcurrent=5Fuser=20?= =?UTF-8?q?from=20langflow.services.auth.utils=20=F0=9F=90=9B=20fix(chat.p?= =?UTF-8?q?y):=20add=20missing=20import=20statement=20for=20HTTPException?= =?UTF-8?q?=20from=20fastapi=20=F0=9F=90=9B=20fix(chat.py):=20add=20missin?= =?UTF-8?q?g=20import=20statement=20for=20get=5Fcurrent=5Fuser=20from=20la?= =?UTF-8?q?ngflow.services.auth.utils=20=F0=9F=90=9B=20fix(chat.py):=20add?= =?UTF-8?q?=20missing=20import=20statement=20for=20HTTPException=20from=20?= =?UTF-8?q?fastapi=20=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import?= =?UTF-8?q?=20statement=20for=20get=5Fcurrent=5Fuser=20from=20langflow.ser?= =?UTF-8?q?vices.auth.utils=20=F0=9F=90=9B=20fix(chat.py):=20add=20missing?= =?UTF-8?q?=20import=20statement=20for=20HTTPException=20from=20fastapi=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20stateme?= =?UTF-8?q?nt=20for=20get=5Fcurrent=5Fuser=20from=20langflow.services.auth?= =?UTF-8?q?.utils=20=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import?= =?UTF-8?q?=20statement=20for=20HTTPException=20from=20fastapi=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(chat.py):=20add=20missing=20import=20stateme?= =?UTF-8?q?nt=20for=20get=5Fcurrent=5Fuser=20from=20langflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/chat.py | 13 +++++++++++-- tests/test_websocket.py | 13 +++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index 459abcf51..83d85b719 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -2,6 +2,7 @@ from fastapi import ( APIRouter, Depends, HTTPException, + Query, WebSocket, WebSocketException, status, @@ -12,9 +13,11 @@ from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, St from langflow.services import service_manager, ServiceType from langflow.graph.graph.base import Graph -from langflow.services.auth.utils import get_current_active_user +from langflow.services.auth.utils import get_current_active_user, get_current_user +from langflow.services.utils import get_session from langflow.utils.logger import logger from cachetools import LRUCache +from sqlmodel import Session router = APIRouter(tags=["Chat"]) @@ -23,10 +26,16 @@ flow_data_store: LRUCache = LRUCache(maxsize=10) @router.websocket("/chat/{client_id}") async def chat( - client_id: str, websocket: WebSocket, current_user=Depends(get_current_active_user) + client_id: str, + websocket: WebSocket, + token: str = Query(...), + db: Session = Depends(get_session), ): """Websocket endpoint for chat.""" try: + user = await get_current_user(token, db) + if not user.is_active: + raise HTTPException(status_code=401, detail="Invalid token") chat_manager = service_manager.get(ServiceType.CHAT_MANAGER) if client_id in chat_manager.in_memory_cache: await chat_manager.handle_websocket(client_id, websocket) diff --git a/tests/test_websocket.py b/tests/test_websocket.py index dd668c287..16f9eff05 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -1,13 +1,16 @@ from fastapi import WebSocketDisconnect +from fastapi.testclient import TestClient # from langflow.services.chat.manager import ChatManager import pytest -def test_init_build(client): +def test_init_build(client, active_user, logged_in_headers): response = client.post( - "api/v1/build/init/test", json={"id": "test", "data": {"key": "value"}} + "api/v1/build/init/test", + json={"id": "test", "data": {"key": "value"}}, + headers=logged_in_headers, ) assert response.status_code == 201 assert response.json() == {"flowId": "test"} @@ -24,10 +27,12 @@ def test_init_build(client): # assert response.headers["content-type"] == "text/event-stream; charset=utf-8" -def test_websocket_endpoint(client): +def test_websocket_endpoint(client: TestClient, active_user, logged_in_headers): + # Assuming your websocket_endpoint uses chat_manager which caches data from stream_build + access_token = logged_in_headers["Authorization"].split(" ")[1] with pytest.raises(WebSocketDisconnect): with client.websocket_connect( - "api/v1/chat/non_existing_client_id" + f"api/v1/chat/non_existing_client_id?token={access_token}" ) as websocket: websocket.send_json({"type": "test"}) data = websocket.receive_json() From 17cbc64273545086869e7ad273bb40047b5ca61d Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 25 Aug 2023 17:17:09 -0300 Subject: [PATCH 133/134] update browser route context location --- src/frontend/src/contexts/index.tsx | 3 +++ src/frontend/src/index.tsx | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 64142f942..acc7f4070 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -9,11 +9,13 @@ import { LocationProvider } from "./locationContext"; import { TabsProvider } from "./tabsContext"; import { TypesProvider } from "./typesContext"; import { UndoRedoProvider } from "./undoRedoContext"; +import { BrowserRouter } from "react-router-dom"; export default function ContextWrapper({ children }: { children: ReactNode }) { //element to wrap all context return ( <> + > ); } diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx index 2542f4903..db196080b 100644 --- a/src/frontend/src/index.tsx +++ b/src/frontend/src/index.tsx @@ -17,10 +17,8 @@ const root = ReactDOM.createRoot( ); root.render( + @@ -33,6 +35,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { - ); reportWebVitals(); From d2f5a5d56480373acf0654a5bee15f1461b41902 Mon Sep 17 00:00:00 2001 From: anovazzi1- Date: Fri, 25 Aug 2023 17:21:54 -0300 Subject: [PATCH 134/134] moved api interceptor --- src/frontend/src/contexts/index.tsx | 2 ++ src/frontend/src/controllers/API/api.tsx | 1 - src/frontend/src/index.tsx | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index acc7f4070..b213ace9d 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -10,6 +10,7 @@ import { TabsProvider } from "./tabsContext"; import { TypesProvider } from "./typesContext"; import { UndoRedoProvider } from "./undoRedoContext"; import { BrowserRouter } from "react-router-dom"; +import { ApiInterceptor } from "../controllers/API/api"; export default function ContextWrapper({ children }: { children: ReactNode }) { //element to wrap all context @@ -23,6 +24,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { + {children} diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index fc4358ed0..0e74670b0 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -24,7 +24,6 @@ function ApiInterceptor() { async (error: AxiosError) => { if (error.response?.status === 401) { const refreshToken = cookies.get("refresh_token"); - if (refreshToken) { authenticationErrorCount = authenticationErrorCount + 1; if (authenticationErrorCount > 3) { diff --git a/src/frontend/src/index.tsx b/src/frontend/src/index.tsx index db196080b..3a7cbd9f5 100644 --- a/src/frontend/src/index.tsx +++ b/src/frontend/src/index.tsx @@ -18,7 +18,6 @@ const root = ReactDOM.createRoot( root.render(); reportWebVitals(); -