From 04cfdc2bf3b15bc9d9bdc5b0137d0d729c0ccadf Mon Sep 17 00:00:00 2001
From: Taylor Helsper
Date: Thu, 29 Sep 2022 23:06:45 -0500
Subject: [PATCH 001/134] Update index.html
---
lnbits/core/templates/core/index.html | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html
index f769b44f6..1319fa1f0 100644
--- a/lnbits/core/templates/core/index.html
+++ b/lnbits/core/templates/core/index.html
@@ -171,6 +171,19 @@
+
From fb58f1ed5e04895582ac79e889a74e2d9a07dd57 Mon Sep 17 00:00:00 2001
From: Taylor Helsper
Date: Thu, 29 Sep 2022 23:42:38 -0500
Subject: [PATCH 002/134] Update myNode images
---
lnbits/static/images/mynode.png | Bin 3757 -> 13994 bytes
lnbits/static/images/mynodel.png | Bin 14352 -> 9272 bytes
2 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/lnbits/static/images/mynode.png b/lnbits/static/images/mynode.png
index cf25bc586797b750a64ddba848b2499a0698a5b2..390446b8ee906da9e01142b477a59506269ce5e7 100644
GIT binary patch
literal 13994
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
diff --git a/lnbits/static/images/mynodel.png b/lnbits/static/images/mynodel.png
index b8afb9ffcd0dbe9c3c07cbd1e007b1067d92caa9..344b54b6db53406258f7f1207b78f72b9fb1cbf3 100644
GIT binary patch
delta 5190
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
delta 10444
zcmai&LvSSwfP`Oc+qUgYG_h??Y&$QQ*tTukwr$&XX8+pS`&RX(tNPNn{<=*v&LNHr
zh~w?0s_CL^0nB1>S1XH0C;RHXJ{mlcRGpvtA}L-(WZwI0+?-q
zq|P3e-6-7(M#yB$a~E`$me8W%?C*o7-+XQM>c295Rpgtd%3QiGUA!kt5IR1HzUCTS
z`XUJS*50oE{Pf;^Ue-+2{tN3$ddm?6{=>h&d>*{~!tePJ=s`s9+5P96cS|&IRQmAv
z>tq#X;N$!G*|l%&AOGActnypd`sONB4`R~6YQ)2Bs=9=IuU^J=E!$~r>c?y^PqSvE
z^!O^iueIx)bF3iOg{Ah{wxf^L6U|vHR?olh@0GEP11S>Eo)Pq4m9igr3rF53Kx@hI
z;oDSYD<6jPt^sfDaa7IoH{N-773!1P=69_^;KOCFla>LqC-d8UGtuY|7!kua2mi*G
zm&8Zc&!f=u=RR{wTr8h-}yM>
za`m^b!NF%Z7o*yxZ`DKRee0bw(7P9^w=>rdjKJBCO(_x>g?xUGz@wSAAM)69Tc>3l
zL&e!M+Gbg=fB9rQB=-ph*lUI3YGF?(Mug|olE{G{yTd8g@2|$EelO+CR3~K6D`6%sd)O7>^>jo&}$_vu}$arrgUZi1eVu_Bc^Da
z%s|NZ3WjP@d=059=W=`p@2&$&_}D*r0Tc&Y!NY&WpnMR@|Kj>Y)&~m_@O`IO@t5Fh
zIPm>dE~uYKQ>kDNrPCQca(b52Bo&Xb0q`bb^T{N=+9hX=|UG
zCTXcG|6Sgwu<&SEa-ypSwyb#J*}g0}Gj+eLcoX@?a3=(nOd1kO5SdJkOHoPW_;@|U
z@M!2-9;>PA**bY&(z6!iFS&ZWZ*(_(c;&Cc)3!NXS$O~LQvCVV|U6*9q
zw?DUR-nKnTtoC$#c(49kZ2`BM@0Oe2aFg*3Heze;ove35p$ig$1v&HeX
zK$jb$&lfB`w($O!T+2VO+jJEQQAG9`^^Z{L-?u<_;A_x7D5wpXJ1JuOT83Fy!xgoz
z=FI4h*`5JjqbU`jNIj-7r*;NY+{3l>Z(9Y;{S#Nw;#D8BYSmSGbC}plBQ(tK7H4iy
zZf-PqgH#2((qxKOB@xD6zum%=Efb4EGefYM&%9v!V&XD=gNpE$nR#T@sp7q^ky$mv-re0}aoSTAXk;&@qHy4NpVy
zk;go0=&YpEi8pZBMiapJ*ZM)%k+E)yMa+Dpe@-3Pm9MxCnyCM=?P9?ZlV;Me=4R)Q
zVvW7J0nuO?kT@m$X|W#iOQnDrk$(5LfLGSrzqcwg!conMy)P_$=>DH>?r0U9Xc$&X
z0bDFK@l7)NQTk$~1?bNPO7YN)#Y7>P#k3VSEBfe=Rac!}3Z_dRR<(TM?8DFebOGM@
z^|~1#I;AoUoqvd0DC^lcqcdKT`lO9VR}6H2^%rAcVRG|o;dW~6WQ`=p=3=8QBD1fcSL-hSQv
z(F4L^)UL>(h{qxKsH@-QY~uh7$wnoh;H?JUrqhz4Ht&`E(~bnuq9?S~HZ&bpH{0Jh
z2tZ2{!jCYZb4IqwakPwL(r7W?wcV0Mils2aS_UhAE%B8>v9;OVaW4w!sLvZwyf;Xy
zTheOO8EK;eVuvpCA%+GP!RRrA{;u3U*CogO#%*QR41nuB^5r6=rE>LOc;!jzjo@_dHM2ekb-t}RcrzNDAN9>5Hd$8sa)L}~
zS8?y-Q71I_&lSwFs8doFgptwF5xnuBQ%@Le50aXYL{XE#)f787d*lSh*-`s}ev3uV
zsZcW0Qwm0}rHq0LQ>ZtcT-QWz;FJJmku;3tIiT^>jVJg{@;;i&4Vf^WAwGjI*NQrt
z;&gmKE9LM_c(ucW!J(VbAp&X4PL4|1jvT1l5Z1*)2>#x6V_ZoX(px74u@49W)Z!7fhPMH;83apV4hJp-ABHRHZb}Pif
z9XAsq#t_5+johxfcwYHmP4y*c^TJ5@%);?EY+IjgQRXzwM0|6s5rIMN*aeLgB}xEM
zev$y0SqDb=dAU8ji9m@5X?puqmJU&F)}tz!V2FA@&jn;*Pj+d+)xS%5K|Uu#Rj-Od
z1frjHFGcScy^Kyvb>ddM9uPoAl=<2_><3(Dfir1kVHH9YI|Nw`Sa)SLr-N!Y24g
zh}O1)M)=3jmSSaDKc|9wYXU)v3V(961?hUTuyUwESA3X6SD<|H=Vd)TZKd+b&{?(q
zbqUNd0br7QPp0e1jC=)SinHO?&}-LC=zucyYIyz~S0w{>BjjCk@CGP?o-zAqA?sb7
ztxI@p<$^pRpVtSHMb=%*8vo5{R+S%aa+gZ%9T})x`!p+hv*&_|Kz2wZs2yVhbDSos
z#tJJM^cK#cAy${lnS!7y6rsp?TNuHUZ*#chs}#pCNIO={67245*$PdmIH$f@-wY5I
zzeti|bG8Uu%8O|7&IS@V=n#jL=VCBZj1ZiF%-M-lOAhHJ_y|fl3Y$#?dWUH`{pfinIM;pgf3c#(pD|+t~NoPpPTN=yUL5U!2DvTjyoANs!N!D?L
ztr$o{OXu2!h2F#IeC>8H^-Qjl)+%Z^BIJ8;t^bSb_p^viLBpcar1^s_Cu7?;6M($
z{Bh8O1=*y-PxKYwRw#)@=%Fs?K$KzbP=4ziTA>HIvGQu+Mq0zNj8ygaLPfIo@ixjQ
zGkvgh4REZ2p8}&k@ef?~swS3y_T8E}?yeXs_0B~39vI`r3+rG&N^I4N;h>Ch62Zqm
zZQ}>05beOpCl8D*oEII0`SAJ8!h=?u_5yB%g9EYCKpIyLnn>f%La-2Hy8>Dbm_JF
z;)WvjoqdY8Lw!JAWK9ObJditcmJ#reXN*NBgtqhBW3~m~7)i;mct}|Cet%x~$VE-x
z30DZ`6fDhFESUdsw5mLa-S@EpJ*iBN=h$o}i2Yk6G$QnAgr%;Xc|~;2O&sr!A)A-W
zOI(_8*9VN(Wia!*#+K_NmdXqD8LYw>^*4UP%!&>v8O!MQF$x*wMUC;2U9dT_#o|R?
zG#TK|>AwbVJL~T-VBx+}R^Vk>yh`@K108k}hEfTMoKdh(s~5!?ej0BXH@2a9na{aG
zaJ!T65D>x7?>cb5*FjOfK#{Eqi0Aq#<**mwPz3UxQD-^9Eso%59(S-R%nS2kker9e
z3d82Rv%}>Oj~muTLZ>@VG?UL>
zgT%Ptnph=!B`=jb#(Oi;P3Xpi!M$SBh1ybcBduHs?A5X=pllg?eu7FmWQIpP!u?
z83D?M#4r>KK~;?EQ&EhajrXnfRp0+nQx@BXYIC^n=d@c;8u3F4DVv${N5&50VsFCZ
zj=sY;%WsPk#4(xZq#7F3rL7}dRHRzcxvfet)U<4=I|-9b9KT6DgByoB&a!49)i06|
z6KT*4#9vpAD;26&F~QE!%KIG_`XbAzm<9aW&r8)fL3h(ph(PvO{k}@Cu8c%0pC=s(
zWpu#xNc!`)o=mBaP}B%hNBS10I>X0*%ScklC~r+Wu;3hhVqI!o!wsGPfST?pnq!Sh>9kVo(DqJC$VMBO@&qB{tiVMj-N*y=@cSk#TrPl
zk|Qdqo`&FK$Yh$0s(x+1zEct~P{@I}Xql*20jb2;%=7y$CJ*fKL7nScM9*4h5-haaPt;6
zi3&rW9+lU-PH;2<=SWuMlt@U?k$$4T%5OtJ1g+~OJ<`wQr&8%%5vq;J#`%=Xq67V{
zj63bX4+PN8FZR03uuT%wOTK#a#q*iz3YPD@uHi)&Aki(zKxzP`{SiLPtO6S@XM4zT1gG!$
z4UHgm1=(R&B#-pQ-?KVSGY+b<;#e3d?IT{H!I^bI5W4M}Fkhy7!
z>NzR}XGKSv^0oUI))UBQ-p#uUEs+#7LiR5%?vz+u)(N~7|C_`G3`o31XeSsGcu0C*
zaWpBheD?$HP$Fdq@ya|P=6I7Vl5i~;za{PiT+tcUGWZ^BFMcRl;9NW2duf;dIQHb5
zBaAoRPkI~Tqz%*T1JJnTC1PWmmH{ECuK*m2C?Z<&LBpve+p*k&0SDacH_}6ZII(Cm
z58=}D1Rel7D7G=Mdp1jQQ|wM@>Aa#Q_t`zz)^T|G2j0*T=2wOQ6O|Ta%HB{^)diGIrB}*Fodh>2UFe#!xqc
z8mF6Y!GecS*pc*cypl4=oM34aF?sX>hWB01LAth2#EhOGUNcC{@Iga~8fG?-L;1q0
zrebn&7WDmbL;ni+!j0D)7TQZo!gv2Ra$u?h=Q+@V0SY6Y?>w;U@WmH#MTEUqZdeV1
zV?0J%U=#Q#?1(wzaE)lzsCRy9aCX49dR$_9UEI%Poq{JTmr8M1FK~8w1P3_$B|boS
z(G*7w@6SYXAf&yB9HrR^ZS${3sT)GtL!5w6inlOOLf5j5xam4BuaCf9&;J&V3w$|`
zK?^r&1SSo#8~x51F)e#(!05d?#MJnjKxJ}sqi|nMx8P?tdv0-e%E*ohB>X90grG|$`vh37FF(i_O
z-GZnl0x(yO-mby74Hn7ayn2XzJEAnSMLM4r`JHnGJ-W?Romv-md{KruLnvEwMItc(
z1gAb37vp=|u8-8F&Gg(wU>Nr>BP4Nz{nNa#&+I?oq)
z-j}U~lolor=+`r@XY1)2Lzf{Qhl^V`1lA!P1;fx{6dSwcGfa7UE5WwLxu{r>#y#N5
zNfZ1T=BIYf|hp=cq&1l*^{9jU|R_sJA`1ivj9^cdpwc)
zBir^C!ZEJ?L+tx&Fv~_MiY$zvg?DnSC;+uX388-+vr`Eq+9io(COpu*wBP6q}
zy+E2pd60k`qbe03Nl?_Gh@SFae*+)v53_~e3d^o=jViX?fHcd%#gaQSk@{$!vVg$Tu5Hn6Edr@NhRrwTcTn5OXGt~@{U}1{B(C|MTAP<8fe1L?tHm3#bw6&%t@s#!6&pzal_N$N^J}yDL5>BRtq-
zQesS>yFZa3s++1W>adwpdMU%f7Tk`SBj%Gt~hJ^Y{+DP_EEK7iuN)Oso`Ku
z3`*T7I;b3R|LjfuhC^R+8*0nwb4fJ)gYN*w48T?Sj#Re_xD*y3h9xLbAi0Ag?P`6H
zMi_}q*8BCQAX5-{XIDi`$}eDB0RY{M!Xu$U9B;pwiI3w8J=Q%HI0sSD_w39IbB?Qo
zKQ4ap@m*qMqfac$Qv5Ug4
z4{dU9u7tvi#v{SYqN)}3#P)q~TMQ8+BNAV4gE>FQ93ao=U+>Gp@!P}-Yj|OU38O~A
zO@G}cuq~37nw6DhRy`UB*SM7jk-CfcT2U)2PYN>vRtiV!wgt&moiNO`*!6w-!kFn|
z4T<}YfSp?iD<~2}@D#3U@u6TIOL#U8zoKKI5@^7x@r`)Zgj=>31jGbWybvskf?u25
zP+QEs7~+tRWiSWLVxER2iY*QZ#ilpc=w1N0paF=ZZXq??vb>}bR<`8k1LlP-UH}L|n>fj=)I;G(nBSeN_6>HP2D%KSwo$p2N4*(`vQ%TYpdP}-3LN9
zSc(0hc;_-b-VO;Q=u)4nGp$wiWH8?GZw~?Ng3LI0OVgSH2&7!7Frk(_?L84RZ7g#iSu78GN?3?k_Sf7O500AqbNg)15R(bNhbYv!=XM8<
zfe&V}p&QOp9iwKKNFTBGs+eoT<*Z>^8ac&t_fg)0Ketv>+`wz3((`7v5qsE_I=ixg
znTfXUWq76vIDt@kwuAU3dLOhFvp|pfE8M++oz=$en>{_`SI!BiwWFJ(-A4t3ZVb=x
zpxICo3=D>VWyuT*qPKEtpue}Yh^0u?jw~AnSAnuTBUC|vWUJ-aX4NBqanun@e5zC%
zS#s(4;MeWz#;8`erR~&X9`waZUU$s^Vf~>mWeN!pXwT#?Hyg#mU3T!NSJgxC7?{+9-&m
z4ukr#ujdZ{06>jpB*XxKifA&{#1d>m*kf=6bP8^DO98FK8Eh&b0+W=Ou&T$#b+)IA
z+H&{kuU=cU{|R42cfy=IWIQJzL@3
zL@Sx!;I8j|qRNaf+)y+rQdQuY6y2
zPG7&Ly$whsidDja_>-V;VH9v-5JL$NL!idvpvEBee?aQ}H5>j{LiAcf^u#Ij#F)r3
zn8^Q|{J-!~ZnhIWTmzNo(B3ZR_cKiFyA~g{d;HS9I9+$rQrvCleTKfFTuLk@&vNSW
zwvJBE34rmcZ{);NNhxjIzLIeNY<;Gl8v>?4CKT3X8
zLZpU0=$+juX^GW&xKGH_wK``@u3XQ@s@yNWV
z=cJ~q`0DovrA!7#+#iAJKLR3FFs^XNI?=J;(h7{5HpXcw;*wu{@}(&!<}zS~wm>k+
z%gNNaoEkMwNR%38P#_SCt{A0U&rM<&;DH0l*Ou@l0o=S__%qCdCpGy^-tGcBE`
zrosIaf8?zrm|l!RceVi=(rROxA3f`d?vyBr@PO;qQD=QLP=={F`uf8ZCxPd(R7Rpl
z4g+!4EKnzEeX7{xp@OR$XZrd9+g&jy00L(epOIpAwM*e=Ojm&1$SdRP6e#a^VEHKo
z74T$4LP65FJ-v0*dg+<;67Zs2qIezO`HFpGy<6@ZR6$$WHn(~y*xg#cYEc^c^Sdy)
zo4mDW31{=Kyz#ou;sEz|Eh}-XuyPi5S+*4Sr)gtN@^^FiF|M6pkuaA&CI2kFC^c-Y
zDTTzvjLB3~Tt(2-iGpx6B9Ke#yBG{jQ~Y#p<&$S5=;#5RO>|SnBOnsDNetxVF|D>c
zHfl)@(wPr3@1OHw>iu9ARHYze$LoM^B?o-WJ}OhZ(s|OklOycs^a9-9kE4x*tbNp^9X@
zm+`^JohipvI%;}6#M_{}{<{QS*#9=5JFs^Rndn-2b(PhFXMY)ym8x!_C_mRfF@CDG{48F6y+{1gufYhX{BVzdSXKb2N8K>etSfHw7c3IMF4)
z%;t;a^Wuv^>s}w1!x)jj%%?L}S?IE~yTX#V$+)>BgZN^<L{OwJr(!Xx(e_K9FGO%ob~jjS6WBT?m3^#Kzk;SMtbW}GV5$@|U0a(Lo?
z3tt^ja-+6EKjY`cI8Bs22@ed0_Y-<%6L^#&rIp2A8n{LaR+zLiu&%f7BF9(5uk2oO
zRMZyB$g*R{k`5AiHIYV!5IO4Q31}dnn7rP({{$0^jy|w5x|5^
z9+Yq~NK}Ht&xUW8;UP`oV>x#?M;Fb-PJZ6`GC8}cjC9(F@?))gxnB{4Gc
zoHWd9R&1KFdE+Bq8W(1RgI#;pM7cP`o4)c7g+ib5&l>~0FWA=*kcB4_xYdvF!g@u5
z_XVT_t|M7AtbECSvP^NcpXrN6@+IY
zg%RceJ8JLm1ac=OTRs3RSfSlSU6&Q6BW9S_FY4KMGX8TzK!UK;tt2*^&ny9gc?u^<
zC$JB?4KILu*B*8m%CFoid8Dy?uZQ7YN%*^5)=QZp)j2_bVp9r|r<8)tGEs2c;bLc6
z-)7a!Lh?li9%S>!;0?!HYN0ANCZaBZt_;pS=X|Am%XNk`m1*3|kU^p>zhBh-E_Kvo
zgGq}W26K}wU4yj}AfaSU2(~dPL#Tm>1tgO7|``sPMp1w`c1Jw->NbF3J7@r
zH0jZ!%7=yMP!s|Q?h-}V#6C@xc6{~9icXz@59b)P=v5m2(80ZZjCVKR2M5fbcw?th
zwar3}i3%d{OvsSDR0I1SQhsR1ow5@y!m?TUk8aky!Y2@s?
z!GDN=#deMQmV#*6d^}X2l7TEAz(hedW(pv*hi8>0(A
z>a>kHk`50zchlpqPrEJ>{ViEu_X2nR`1BTIq2XS_P
zpu1XSM(6LHIFR9;#}h#^Y|&qT@hjGcp{`^~@OqT%JN{)S0!WHH-Y|F;fv@7evh<06
zQqrB?lO?h3$&b=(FLNg|h-h4s^D)>c!|_lZDl$Qfj_^*%hg!*2174^_smaAj^v?PX
z3#YCKV;FpTjJ6O0rf5^C_>rIfSc>sK3;wg-Y7g-YK!|+V>PxC(FA6?CM!w9VXR*-p
zNLHbfJLz0z9rvZx8+fB~1?D)qzddf^^E&=Tg#SaH%jL{%x^wT$5h^N8m|B+uOod@h
z`DchmMoQJg=WvSY_COXvT_hm-PK$I0(+d|+&QVA&7ZUtH?jGW%iTBI62IkF{E$MVDXm)S(
zaTlJu_+g&yglh3;1&2t&`RB`-2%uQmAU4qYjL;e{{~-V78ue>bH&9sWq5K{0-86zf
zYh8b--iE|!3j^cXH}_|LNYfiaQ>0C3h+GL0aoBYL@Uy9)e3Cv-Aq;-I&YJ@CyO&Wwr4uW@+qJdA@Zuxp|Yj)
zJW4#z%(_SGWkrE2D7iqfv3j-8xm(iN?w5c%`JF(*2~7xaNH0^tt#CElF6jf4X>O)F
zm=5r%WKipMw)wUS%Tq4w;>R&SSmvvJM%NsMCErQYYpm0TfH&}KE@9yHZl3LBR~uF~58C2pDTY9>&dJmiJNh>LB_l
z(v~DY>uy?KCXKq@2zHJPP94#K`p9QD3|Z|mh&J7dT_`*&&h;0tMY58ihPy0(&)F~QehXi&kKqW3bMeytCiOP3${VIzbBDzxa}O~Vbe^x4ZUf097Lp|#l0?ZBu;$A;#b|<-mv`maYNP@l+D*wob$Y%hgV>{gf3>MTd16x3j47&X7=nac{tUxT#N*S-)a_-R=AX1&&
vlqRIN&C`Jse=btw1BT}QUpAqN{{eZm7J;Iw)bRcfx&TtY<;7}53
Date: Fri, 30 Sep 2022 10:19:17 -0500
Subject: [PATCH 003/134] Fix formatting
---
lnbits/core/templates/core/index.html | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html
index 1319fa1f0..68a7b7ede 100644
--- a/lnbits/core/templates/core/index.html
+++ b/lnbits/core/templates/core/index.html
@@ -180,9 +180,7 @@
>
-
-
-
+
From 41668d6f3288fa75ac47be685e338242e0ec85c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Sat, 1 Oct 2022 11:33:58 +0200
Subject: [PATCH 004/134] add warning for voidwallet for better coloring in
logs
---
lnbits/wallets/void.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/wallets/void.py b/lnbits/wallets/void.py
index 0de387aae..b74eb2450 100644
--- a/lnbits/wallets/void.py
+++ b/lnbits/wallets/void.py
@@ -23,7 +23,7 @@ class VoidWallet(Wallet):
raise Unsupported("")
async def status(self) -> StatusResponse:
- logger.info(
+ logger.warning(
"This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits."
)
return StatusResponse(None, 0)
From 3aa4d09103ac94b1e3703086f1528da1020f53e3 Mon Sep 17 00:00:00 2001
From: Indra <86242283+VajraOfIndra@users.noreply.github.com>
Date: Sun, 2 Oct 2022 16:45:16 +0100
Subject: [PATCH 005/134] Adding missing theme options in conf file
Missing bitcoin theme in comment.
Missing flamingo theme in list of values for LNBITS_THEME_OPTIONS.
---
.env.example | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.env.example b/.env.example
index 93b823250..7d6de35f4 100644
--- a/.env.example
+++ b/.env.example
@@ -37,8 +37,8 @@ LNBITS_RESERVE_FEE_PERCENT=1.0
LNBITS_SITE_TITLE="LNbits"
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
-# Choose from mint, flamingo, freedom, salvador, autumn, monochrome, classic
-LNBITS_THEME_OPTIONS="classic, bitcoin, freedom, mint, autumn, monochrome, salvador"
+# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic
+LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador"
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
@@ -91,4 +91,4 @@ LNBITS_DENOMINATION=sats
# EclairWallet
ECLAIR_URL=http://127.0.0.1:8283
-ECLAIR_PASS=eclairpw
\ No newline at end of file
+ECLAIR_PASS=eclairpw
From b6755abc8e9911c34b96d3039140b2b64ff5634f Mon Sep 17 00:00:00 2001
From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com>
Date: Wed, 5 Oct 2022 09:16:54 +0200
Subject: [PATCH 006/134] updated poetry install notes
- referenced option for python3.10 and newer
- poetry install with --only main, instead of --no-dev (deprecated)
---
docs/guide/installation.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
index 87679ed5b..5e9fdb28a 100644
--- a/docs/guide/installation.md
+++ b/docs/guide/installation.md
@@ -26,8 +26,8 @@ sudo apt install python3.9 python3.9-distutils
curl -sSL https://install.python-poetry.org | python3 -
export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal
-poetry env use python3.9
-poetry install --no-dev
+poetry env use python3.9 # you can exchange with python3.10 or newer versions. Identify your version with python3 --version and specify here
+poetry install --only main
poetry run python build.py
mkdir data
From bc2068cc87f5d7b867ce0e9f4ccc8fabf8af7732 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 6 Oct 2022 09:42:38 +0200
Subject: [PATCH 007/134] fix poetry lock in main
---
poetry.lock | 152 ++++++++++++++++++++++++++--------------------------
1 file changed, 76 insertions(+), 76 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 343fffbfb..5b283d758 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -196,7 +196,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
-version = "6.4.4"
+version = "6.5.0"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
@@ -589,7 +589,7 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "protobuf"
-version = "4.21.6"
+version = "4.21.7"
description = ""
category = "main"
optional = false
@@ -659,12 +659,12 @@ optional = false
python-versions = ">=3.7,<4.0"
[package.dependencies]
-pyln-bolt7 = ">=1.0"
-pyln-proto = ">=0.12"
+pyln-bolt7 = ">=1.0,<2.0"
+pyln-proto = ">=0.11,<0.12"
[[package]]
name = "pyln-proto"
-version = "0.12.0"
+version = "0.11.1"
description = "This package implements some of the Lightning Network protocol in pure python. It is intended for protocol testing and some minor tooling only. It is not deemed secure enough to handle any amount of real funds (you have been warned!)."
category = "main"
optional = false
@@ -839,14 +839,14 @@ cffi = ">=1.3.0"
[[package]]
name = "setuptools"
-version = "65.4.0"
+version = "65.4.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
@@ -1051,7 +1051,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
[metadata]
lock-version = "1.1"
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
-content-hash = "72e4462285d0bc5e2cb83c88c613726beced959b268bd30b984d8baaeff178ea"
+content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42"
[metadata.files]
aiofiles = [
@@ -1224,56 +1224,56 @@ colorama = [
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
coverage = [
- {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"},
- {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"},
- {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"},
- {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"},
- {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"},
- {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"},
- {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"},
- {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"},
- {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"},
- {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"},
- {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"},
- {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"},
- {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"},
- {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"},
- {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"},
- {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"},
- {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"},
- {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"},
- {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"},
- {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"},
- {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"},
- {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"},
- {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"},
- {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"},
- {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"},
- {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"},
- {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"},
- {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"},
- {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"},
- {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"},
- {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"},
- {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"},
- {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"},
- {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"},
- {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"},
- {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"},
- {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"},
- {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"},
- {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"},
- {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"},
- {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"},
- {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"},
- {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"},
- {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"},
- {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"},
- {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"},
- {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"},
- {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"},
- {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"},
- {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"},
+ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
+ {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
+ {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
+ {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
+ {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
+ {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
+ {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
+ {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
+ {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
+ {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
+ {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
+ {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
+ {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
+ {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
+ {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
+ {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
+ {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
+ {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
+ {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
+ {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
]
cryptography = [
{file = "cryptography-36.0.2-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6"},
@@ -1573,20 +1573,20 @@ pluggy = [
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
protobuf = [
- {file = "protobuf-4.21.6-cp310-abi3-win32.whl", hash = "sha256:49f88d56a9180dbb7f6199c920f5bb5c1dd0172f672983bb281298d57c2ac8eb"},
- {file = "protobuf-4.21.6-cp310-abi3-win_amd64.whl", hash = "sha256:7a6cc8842257265bdfd6b74d088b829e44bcac3cca234c5fdd6052730017b9ea"},
- {file = "protobuf-4.21.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:ba596b9ffb85c909fcfe1b1a23136224ed678af3faf9912d3fa483d5f9813c4e"},
- {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:4143513c766db85b9d7c18dbf8339673c8a290131b2a0fe73855ab20770f72b0"},
- {file = "protobuf-4.21.6-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b6cea204865595a92a7b240e4b65bcaaca3ad5d2ce25d9db3756eba06041138e"},
- {file = "protobuf-4.21.6-cp37-cp37m-win32.whl", hash = "sha256:9666da97129138585b26afcb63ad4887f602e169cafe754a8258541c553b8b5d"},
- {file = "protobuf-4.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:308173d3e5a3528787bb8c93abea81d5a950bdce62840d9760effc84127fb39c"},
- {file = "protobuf-4.21.6-cp38-cp38-win32.whl", hash = "sha256:aa29113ec901281f29d9d27b01193407a98aa9658b8a777b0325e6d97149f5ce"},
- {file = "protobuf-4.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:8f9e60f7d44592c66e7b332b6a7b4b6e8d8b889393c79dbc3a91f815118f8eac"},
- {file = "protobuf-4.21.6-cp39-cp39-win32.whl", hash = "sha256:80e6540381080715fddac12690ee42d087d0d17395f8d0078dfd6f1181e7be4c"},
- {file = "protobuf-4.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:77b355c8604fe285536155286b28b0c4cbc57cf81b08d8357bf34829ea982860"},
- {file = "protobuf-4.21.6-py2.py3-none-any.whl", hash = "sha256:07a0bb9cc6114f16a39c866dc28b6e3d96fa4ffb9cc1033057412547e6e75cb9"},
- {file = "protobuf-4.21.6-py3-none-any.whl", hash = "sha256:c7c864148a237f058c739ae7a05a2b403c0dfa4ce7d1f3e5213f352ad52d57c6"},
- {file = "protobuf-4.21.6.tar.gz", hash = "sha256:6b1040a5661cd5f6e610cbca9cfaa2a17d60e2bb545309bc1b278bb05be44bdd"},
+ {file = "protobuf-4.21.7-cp310-abi3-win32.whl", hash = "sha256:c7cb105d69a87416bd9023e64324e1c089593e6dae64d2536f06bcbe49cd97d8"},
+ {file = "protobuf-4.21.7-cp310-abi3-win_amd64.whl", hash = "sha256:3ec85328a35a16463c6f419dbce3c0fc42b3e904d966f17f48bae39597c7a543"},
+ {file = "protobuf-4.21.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:db9056b6a11cb5131036d734bcbf91ef3ef9235d6b681b2fc431cbfe5a7f2e56"},
+ {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:ca200645d6235ce0df3ccfdff1567acbab35c4db222a97357806e015f85b5744"},
+ {file = "protobuf-4.21.7-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:b019c79e23a80735cc8a71b95f76a49a262f579d6b84fd20a0b82279f40e2cc1"},
+ {file = "protobuf-4.21.7-cp37-cp37m-win32.whl", hash = "sha256:d3f89ccf7182293feba2de2739c8bf34fed1ed7c65a5cf987be00311acac57c1"},
+ {file = "protobuf-4.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:a74d96cd960b87b4b712797c741bb3ea3a913f5c2dc4b6cbe9c0f8360b75297d"},
+ {file = "protobuf-4.21.7-cp38-cp38-win32.whl", hash = "sha256:8e09d1916386eca1ef1353767b6efcebc0a6859ed7f73cb7fb974feba3184830"},
+ {file = "protobuf-4.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:9e355f2a839d9930d83971b9f562395e13493f0e9211520f8913bd11efa53c02"},
+ {file = "protobuf-4.21.7-cp39-cp39-win32.whl", hash = "sha256:f370c0a71712f8965023dd5b13277444d3cdfecc96b2c778b0e19acbfd60df6e"},
+ {file = "protobuf-4.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:9643684232b6b340b5e63bb69c9b4904cdd39e4303d498d1a92abddc7e895b7f"},
+ {file = "protobuf-4.21.7-py2.py3-none-any.whl", hash = "sha256:8066322588d4b499869bf9f665ebe448e793036b552f68c585a9b28f1e393f66"},
+ {file = "protobuf-4.21.7-py3-none-any.whl", hash = "sha256:58b81358ec6c0b5d50df761460ae2db58405c063fd415e1101209221a0a810e1"},
+ {file = "protobuf-4.21.7.tar.gz", hash = "sha256:71d9dba03ed3432c878a801e2ea51e034b0ea01cf3a4344fb60166cb5f6c8757"},
]
psycopg2-binary = [
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
@@ -1696,8 +1696,8 @@ pyln-client = [
{file = "pyln_client-0.11.1-py3-none-any.whl", hash = "sha256:497db443406b80c98c0434e2938eb1b2a17e88fd9aa63b018124068198df6141"},
]
pyln-proto = [
- {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"},
- {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"},
+ {file = "pyln-proto-0.11.1.tar.gz", hash = "sha256:9bed240f41917c4fd526b767218a77d0fbe69242876eef72c35a856796f922d6"},
+ {file = "pyln_proto-0.11.1-py3-none-any.whl", hash = "sha256:27b2e04a81b894f69018279c0ce4aa2e7ccd03b86dd9783f96b9d8d1498c8393"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
@@ -1799,8 +1799,8 @@ secp256k1 = [
{file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"},
]
setuptools = [
- {file = "setuptools-65.4.0-py3-none-any.whl", hash = "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1"},
- {file = "setuptools-65.4.0.tar.gz", hash = "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9"},
+ {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
+ {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
]
shortuuid = [
{file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"},
From 79319faa2146da17cd22467d9dd4762c42540f67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 6 Oct 2022 10:17:21 +0200
Subject: [PATCH 008/134] make workflows only run on pull_request
---
.github/workflows/codeql.yml | 2 --
.github/workflows/formatting.yml | 2 --
.github/workflows/mypy.yml | 2 +-
.github/workflows/regtest.yml | 2 +-
.github/workflows/tests.yml | 2 +-
5 files changed, 3 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 876c8b8af..bbe959837 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,8 +1,6 @@
name: codeql
on:
- push:
- branches: [main, ]
pull_request:
branches: [main]
schedule:
diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml
index e3d0fd35d..21c7fb38e 100644
--- a/.github/workflows/formatting.yml
+++ b/.github/workflows/formatting.yml
@@ -1,8 +1,6 @@
name: formatting
on:
- push:
- branches: [ main ]
pull_request:
branches: [ main ]
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index d80da678a..96b574d2d 100644
--- a/.github/workflows/mypy.yml
+++ b/.github/workflows/mypy.yml
@@ -1,6 +1,6 @@
name: mypy
-on: [push, pull_request]
+on: [pull_request]
jobs:
check:
diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml
index 2d7aae6b3..f0adb3ac5 100644
--- a/.github/workflows/regtest.yml
+++ b/.github/workflows/regtest.yml
@@ -1,6 +1,6 @@
name: regtest
-on: [push, pull_request]
+on: [pull_request]
jobs:
LndRestWallet:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 5d368fbb1..c7b6e44bb 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,6 +1,6 @@
name: tests
-on: [push, pull_request]
+on: [pull_request]
jobs:
venv-sqlite:
From 63e4f7d59c0505654d47b8793738211966b9b776 Mon Sep 17 00:00:00 2001
From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com>
Date: Thu, 6 Oct 2022 14:25:14 +0200
Subject: [PATCH 009/134] changed comment places and small adjustments
- #comments in own lines
- cleaned up some ominous settings
- added --debug option for running the server
---
docs/guide/installation.md | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
index 5e9fdb28a..37104fde2 100644
--- a/docs/guide/installation.md
+++ b/docs/guide/installation.md
@@ -18,21 +18,25 @@ If you have problems installing LNbits using these instructions, please have a l
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
-# for making sure python 3.9 is installed, skip if installed
+# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt install python3.9 python3.9-distutils
curl -sSL https://install.python-poetry.org | python3 -
-export PATH="/home/ubuntu/.local/bin:$PATH" # or whatever is suggested in the poetry install notes printed to terminal
-poetry env use python3.9 # you can exchange with python3.10 or newer versions. Identify your version with python3 --version and specify here
+# Once the above poetry install is completed, use the installation path printed to terminal and replace in the following command
+export PATH="/home/user/.local/bin:$PATH"
+# Next command, you can exchange with python3.10 or newer versions.
+# Identify your version with python3 --version and specify in the next line
+# command is only needed when your default python is not ^3.9 or ^3.10
+poetry env use python3.9
poetry install --only main
-poetry run python build.py
mkdir data
cp .env.example .env
-nano .env # set funding source
+# set funding source amongst other options
+nano .env
```
#### Running the server
@@ -40,6 +44,7 @@ nano .env # set funding source
```sh
poetry run lnbits
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
+# add --debug for slightly more verbose output
```
## Option 2: Nix
From 36b60ad318fffa87d8ec55753538525531d76dec Mon Sep 17 00:00:00 2001
From: HODLmeTight <35168804+TrezorHannes@users.noreply.github.com>
Date: Thu, 6 Oct 2022 14:50:33 +0200
Subject: [PATCH 010/134] added debug option for .env
---
docs/guide/installation.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
index 37104fde2..6b95f93b1 100644
--- a/docs/guide/installation.md
+++ b/docs/guide/installation.md
@@ -44,7 +44,8 @@ nano .env
```sh
poetry run lnbits
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
-# add --debug for slightly more verbose output
+# adding --debug in the start-up command above to help your troubleshooting and generate a more verbose output
+# Note that you have to add the line DEBUG=true in your .env file, too.
```
## Option 2: Nix
From 27bae6a0c6cce731256095465905c92c1547c1e2 Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 6 Oct 2022 14:16:21 +0100
Subject: [PATCH 011/134] adds atm check, so withdraw isnt cancelled on false
call by wallets
---
lnbits/extensions/lnurldevice/lnurl.py | 29 +++++++++++++++-----------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py
index df0cd4b8f..ec7f3b486 100644
--- a/lnbits/extensions/lnurldevice/lnurl.py
+++ b/lnbits/extensions/lnurldevice/lnurl.py
@@ -184,22 +184,27 @@ async def lnurl_callback(
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found."
)
- if pr:
- if lnurldevicepayment.id != k1:
- return {"status": "ERROR", "reason": "Bad K1"}
- if lnurldevicepayment.payhash != "payment_hash":
- return {"status": "ERROR", "reason": f"Payment already claimed"}
+ if device.device == "atm":
+ if not pr:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="No payment request"
+ )
+ else:
+ if lnurldevicepayment.id != k1:
+ return {"status": "ERROR", "reason": "Bad K1"}
+ if lnurldevicepayment.payhash != "payment_hash":
+ return {"status": "ERROR", "reason": f"Payment already claimed"}
lnurldevicepayment = await update_lnurldevicepayment(
lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload
)
- await pay_invoice(
- wallet_id=device.wallet,
- payment_request=pr,
- max_sat=lnurldevicepayment.sats / 1000,
- extra={"tag": "withdraw"},
- )
- return {"status": "OK"}
+ await pay_invoice(
+ wallet_id=device.wallet,
+ payment_request=pr,
+ max_sat=lnurldevicepayment.sats / 1000,
+ extra={"tag": "withdraw"},
+ )
+ return {"status": "OK"}
payment_hash, payment_request = await create_invoice(
wallet_id=device.wallet,
From 8e8bf08ea587aa9013a966a807600812fee3c780 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 6 Oct 2022 15:21:09 +0100
Subject: [PATCH 012/134] fix issue with splitting to multiple wallets-queue
---
lnbits/extensions/splitpayments/tasks.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index b7cf1750c..cfc6c226f 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -28,6 +28,10 @@ async def on_invoice_paid(payment: Payment) -> None:
# now we make some special internal transfers (from no one to the receiver)
targets = await get_targets(payment.wallet_id)
+
+ if not targets:
+ return
+
transfers = [
(target.wallet, int(target.percent * payment.amount / 100))
for target in targets
@@ -41,9 +45,6 @@ async def on_invoice_paid(payment: Payment) -> None:
)
return
- if not targets:
- return
-
# mark the original payment with one extra key, "splitted"
# (this prevents us from doing this process again and it's informative)
# and reduce it by the amount we're going to send to the producer
@@ -76,5 +77,5 @@ async def on_invoice_paid(payment: Payment) -> None:
)
# manually send this for now
- await internal_invoice_queue.put(internal_checking_id)
+ await internal_invoice_queue.put(internal_checking_id)
return
From 11ec82e33962462880d08a9dc2ab6e828762edbd Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 6 Oct 2022 15:21:53 +0100
Subject: [PATCH 013/134] floor amount when not whole sat
---
lnbits/extensions/scrub/tasks.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py
index 320d34da0..62adc5e5e 100644
--- a/lnbits/extensions/scrub/tasks.py
+++ b/lnbits/extensions/scrub/tasks.py
@@ -1,6 +1,7 @@
import asyncio
import json
from http import HTTPStatus
+from math import floor
from urllib.parse import urlparse
import httpx
@@ -26,7 +27,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
# (avoid loops)
- if "scrubed" == payment.extra.get("tag"):
+ if payment.extra.get("tag") == "scrubed":
# already scrubbed
return
@@ -42,12 +43,13 @@ async def on_invoice_paid(payment: Payment) -> None:
# I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267
domain = urlparse(data["callback"]).netloc
-
+ rounded_amount = floor(payment.amount / 1000) * 1000
+
async with httpx.AsyncClient() as client:
try:
r = await client.get(
data["callback"],
- params={"amount": payment.amount},
+ params={"amount": rounded_amount},
timeout=40,
)
if r.is_error:
@@ -66,7 +68,8 @@ async def on_invoice_paid(payment: Payment) -> None:
)
invoice = bolt11.decode(params["pr"])
- if invoice.amount_msat != payment.amount:
+
+ if invoice.amount_msat != rounded_amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.",
From 7316ef7dbb67a9327597e2e97aa7ef54f7a08ac0 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 6 Oct 2022 15:33:20 +0100
Subject: [PATCH 014/134] add disclaimer about whole (integer) sats
---
lnbits/extensions/scrub/README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lnbits/extensions/scrub/README.md b/lnbits/extensions/scrub/README.md
index 680c5e6db..3b8d0b2d7 100644
--- a/lnbits/extensions/scrub/README.md
+++ b/lnbits/extensions/scrub/README.md
@@ -4,6 +4,8 @@
SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress!
+Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet!
+
[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)
## Usage
From 92a756b9bb92078bb937f4a22c9b35b033f44fed Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 6 Oct 2022 15:34:37 +0100
Subject: [PATCH 015/134] make format
---
lnbits/extensions/scrub/tasks.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py
index 62adc5e5e..852f3860c 100644
--- a/lnbits/extensions/scrub/tasks.py
+++ b/lnbits/extensions/scrub/tasks.py
@@ -44,7 +44,7 @@ async def on_invoice_paid(payment: Payment) -> None:
# I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267
domain = urlparse(data["callback"]).netloc
rounded_amount = floor(payment.amount / 1000) * 1000
-
+
async with httpx.AsyncClient() as client:
try:
r = await client.get(
@@ -68,7 +68,7 @@ async def on_invoice_paid(payment: Payment) -> None:
)
invoice = bolt11.decode(params["pr"])
-
+
if invoice.amount_msat != rounded_amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
From 3222ae198d4f4bda826f1a63f861d5e90d0df4ac Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 6 Oct 2022 17:10:15 +0100
Subject: [PATCH 016/134] Adding bitcoinswitch to lnurldevices
---
lnbits/extensions/lnurldevice/__init__.py | 7 +++-
lnbits/extensions/lnurldevice/lnurl.py | 40 ++++++++++++++++--
lnbits/extensions/lnurldevice/migrations.py | 6 +++
lnbits/extensions/lnurldevice/models.py | 2 +
lnbits/extensions/lnurldevice/tasks.py | 39 ++++++++++++++++++
lnbits/extensions/lnurldevice/views.py | 45 +++++++++++++++++++++
6 files changed, 135 insertions(+), 4 deletions(-)
create mode 100644 lnbits/extensions/lnurldevice/tasks.py
diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py
index 54849c957..dc4456b4d 100644
--- a/lnbits/extensions/lnurldevice/__init__.py
+++ b/lnbits/extensions/lnurldevice/__init__.py
@@ -2,6 +2,7 @@ from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
+from lnbits.tasks import catch_everything_and_restart
db = Database("ext_lnurldevice")
@@ -11,7 +12,11 @@ lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice
def lnurldevice_renderer():
return template_renderer(["lnbits/extensions/lnurldevice/templates"])
-
+from .tasks import wait_for_paid_invoices
from .lnurl import * # noqa
from .views import * # noqa
from .views_api import * # noqa
+
+def lnurldevice_start():
+ loop = asyncio.get_event_loop()
+ loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py
index ec7f3b486..a2bc0dd4f 100644
--- a/lnbits/extensions/lnurldevice/lnurl.py
+++ b/lnbits/extensions/lnurldevice/lnurl.py
@@ -102,7 +102,22 @@ async def lnurl_v1_params(
if device.device == "atm":
if paymentcheck:
return {"status": "ERROR", "reason": f"Payment already claimed"}
-
+ if device.device == "switch":
+ lnurldevicepayment = await create_lnurldevicepayment(
+ deviceid=device.id,
+ sats=device.profit,
+ )
+ if not lnurldevicepayment:
+ return {"status": "ERROR", "reason": "Could not create payment."}
+ return {
+ "tag": "payRequest",
+ "callback": request.url_for(
+ "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id
+ ),
+ "minSendable": device.profit * 1000,
+ "maxSendable": device.profit * 1000,
+ "metadata": await device.lnurlpay_metadata(),
+ }
if len(p) % 4 > 0:
p += "=" * (4 - (len(p) % 4))
@@ -205,6 +220,27 @@ async def lnurl_callback(
extra={"tag": "withdraw"},
)
return {"status": "OK"}
+ if device.device == "switch":
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=device.wallet,
+ amount=lnurldevicepayment.sats / 1000,
+ memo=device.title,
+ unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
+ extra={"tag": "Switch", "id": device.paymentid, "time": device.amount},
+ )
+ lnurldevicepayment = await update_lnurldevicepayment(
+ lnurldevicepayment_id=paymentid, payhash=payment_hash
+ )
+
+ return {
+ "pr": payment_request,
+ "successAction": {
+ "tag": "url",
+ "description": "Check the attached link",
+ "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid),
+ },
+ "routes": [],
+ }
payment_hash, payment_request = await create_invoice(
wallet_id=device.wallet,
@@ -226,5 +262,3 @@ async def lnurl_callback(
},
"routes": [],
}
-
- return resp.dict()
diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py
index c7899282c..65913b023 100644
--- a/lnbits/extensions/lnurldevice/migrations.py
+++ b/lnbits/extensions/lnurldevice/migrations.py
@@ -79,3 +79,9 @@ async def m002_redux(db):
)
except:
return
+
+async def m003_redux(db):
+ """
+ Add 'meta' for storing various metadata about the wallet
+ """
+ await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;")
\ No newline at end of file
diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py
index fef0aec1e..cf6171419 100644
--- a/lnbits/extensions/lnurldevice/models.py
+++ b/lnbits/extensions/lnurldevice/models.py
@@ -17,6 +17,7 @@ class createLnurldevice(BaseModel):
currency: str
device: str
profit: float
+ amount: int
class lnurldevices(BaseModel):
@@ -27,6 +28,7 @@ class lnurldevices(BaseModel):
currency: str
device: str
profit: float
+ amount: int
timestamp: str
def from_row(cls, row: Row) -> "lnurldevices":
diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py
new file mode 100644
index 000000000..2337c0ad1
--- /dev/null
+++ b/lnbits/extensions/lnurldevice/tasks.py
@@ -0,0 +1,39 @@
+import asyncio
+import json
+from http import HTTPStatus
+from urllib.parse import urlparse
+
+import httpx
+from fastapi import HTTPException
+
+from lnbits import bolt11
+from lnbits.core.models import Payment
+from lnbits.core.services import pay_invoice
+from lnbits.helpers import get_current_extension_name
+from lnbits.tasks import register_invoice_listener
+
+from .crud import get_lnurldevice
+from .views import updater
+
+async def wait_for_paid_invoices():
+ invoice_queue = asyncio.Queue()
+ register_invoice_listener(invoice_queue, get_current_extension_name())
+
+ while True:
+ payment = await invoice_queue.get()
+ await on_invoice_paid(payment)
+
+
+async def on_invoice_paid(payment: Payment) -> None:
+ # (avoid loops)
+ if "switch" == payment.extra.get("tag"):
+ lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id"))
+ if not lnurldevicepayment:
+ return
+ if lnurldevicepayment.payhash == "used":
+ return
+ lnurldevicepayment = await update_lnurldevicepayment(
+ lnurldevicepayment_id=paymentid, payhash="used"
+ )
+ return await updater(lnurldevicepayment.deviceid)
+ return
\ No newline at end of file
diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py
index 3389e17c7..4a28a5c1a 100644
--- a/lnbits/extensions/lnurldevice/views.py
+++ b/lnbits/extensions/lnurldevice/views.py
@@ -51,3 +51,48 @@ async def displaypin(request: Request, paymentid: str = Query(None)):
"lnurldevice/error.html",
{"request": request, "pin": "filler", "not_paid": True},
)
+
+
+##################WEBSOCKET ROUTES########################
+
+
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, lnurldevice_id: str):
+ await websocket.accept()
+ websocket.id = lnurldevice_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, lnurldevice_id: str):
+ for connection in self.active_connections:
+ if connection.id == lnurldevice_id:
+ await connection.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+
+manager = ConnectionManager()
+
+
+@lnurldevice_ext.websocket("/ws/{lnurldevice_id}", name="lnurldevice.lnurldevice_by_id")
+async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str):
+ await manager.connect(websocket, lnurldevice_id)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+
+
+async def updater(lnurldevice_id):
+ lnurldevice = await get_lnurldevice(lnurldevice_id)
+ if not lnurldevice:
+ return
+ await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
\ No newline at end of file
From 05c8f02cb7cc714457e885c2c9002f6cc97c3074 Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 6 Oct 2022 17:41:06 +0100
Subject: [PATCH 017/134] Added code sample for websocket stuff
---
docs/devs/websockets.md | 87 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 docs/devs/websockets.md
diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md
new file mode 100644
index 000000000..34cf940de
--- /dev/null
+++ b/docs/devs/websockets.md
@@ -0,0 +1,87 @@
+---
+layout: default
+parent: For developers
+title: Websockets
+nav_order: 2
+---
+
+
+Websockets
+=================
+
+`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from teh copilot extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension):
+
+
+```sh
+from fastapi import Request, WebSocket, WebSocketDisconnect
+
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, extension_id: str):
+ await websocket.accept()
+ websocket.id = extension_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, extension_id: str):
+ for connection in self.active_connections:
+ if connection.id == extension_id:
+ await connection.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+
+manager = ConnectionManager()
+
+
+@extension_ext.websocket("/ws/{extension_id}", name="extension.websocket_by_id")
+async def websocket_endpoint(websocket: WebSocket, extension_id: str):
+ await manager.connect(websocket, extension_id)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+
+
+async def updater(extension_id, data):
+ extension = await get_extension(extension_id)
+ if not extension:
+ return
+ await manager.send_personal_message(f"{data}", extension_id)
+```
+
+Example vue-js function for listening to the websocket:
+
+```
+initWs: async function () {
+ if (location.protocol !== 'http:') {
+ localUrl =
+ 'wss://' +
+ document.domain +
+ ':' +
+ location.port +
+ '/extension/ws/' +
+ self.extension.id
+ } else {
+ localUrl =
+ 'ws://' +
+ document.domain +
+ ':' +
+ location.port +
+ '/extension/ws/' +
+ self.extension.id
+ }
+ this.ws = new WebSocket(localUrl)
+ this.ws.addEventListener('message', async ({data}) => {
+ const res = JSON.parse(data.toString())
+ console.log(res)
+ })
+},
+```
\ No newline at end of file
From 6d55445a1bb941ed2b13b290cb4d8994ef0a52ba Mon Sep 17 00:00:00 2001
From: Arc <33088785+arcbtc@users.noreply.github.com>
Date: Thu, 6 Oct 2022 17:44:04 +0100
Subject: [PATCH 018/134] Update websockets.md
---
docs/devs/websockets.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md
index 34cf940de..0638e4f28 100644
--- a/docs/devs/websockets.md
+++ b/docs/devs/websockets.md
@@ -9,7 +9,7 @@ nav_order: 2
Websockets
=================
-`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from teh copilot extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension):
+`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from the `copilot` extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension):
```sh
@@ -84,4 +84,4 @@ initWs: async function () {
console.log(res)
})
},
-```
\ No newline at end of file
+```
From b46c06012d1e4e280eb9a8d3459276489a935b62 Mon Sep 17 00:00:00 2001
From: Arc <33088785+arcbtc@users.noreply.github.com>
Date: Thu, 6 Oct 2022 17:51:36 +0100
Subject: [PATCH 019/134] Revert "make gh workflows only run on pull_request"
---
.github/workflows/codeql.yml | 2 ++
.github/workflows/formatting.yml | 2 ++
.github/workflows/mypy.yml | 2 +-
.github/workflows/regtest.yml | 2 +-
.github/workflows/tests.yml | 2 +-
5 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index bbe959837..876c8b8af 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,6 +1,8 @@
name: codeql
on:
+ push:
+ branches: [main, ]
pull_request:
branches: [main]
schedule:
diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml
index 21c7fb38e..e3d0fd35d 100644
--- a/.github/workflows/formatting.yml
+++ b/.github/workflows/formatting.yml
@@ -1,6 +1,8 @@
name: formatting
on:
+ push:
+ branches: [ main ]
pull_request:
branches: [ main ]
diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml
index 96b574d2d..d80da678a 100644
--- a/.github/workflows/mypy.yml
+++ b/.github/workflows/mypy.yml
@@ -1,6 +1,6 @@
name: mypy
-on: [pull_request]
+on: [push, pull_request]
jobs:
check:
diff --git a/.github/workflows/regtest.yml b/.github/workflows/regtest.yml
index f0adb3ac5..2d7aae6b3 100644
--- a/.github/workflows/regtest.yml
+++ b/.github/workflows/regtest.yml
@@ -1,6 +1,6 @@
name: regtest
-on: [pull_request]
+on: [push, pull_request]
jobs:
LndRestWallet:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c7b6e44bb..5d368fbb1 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,6 +1,6 @@
name: tests
-on: [pull_request]
+on: [push, pull_request]
jobs:
venv-sqlite:
From ff98b87eca0ab2309ccc1b1be33969aaeb96b035 Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 6 Oct 2022 22:19:30 +0100
Subject: [PATCH 020/134] Added a switch
---
lnbits/extensions/lnurldevice/__init__.py | 1 +
lnbits/extensions/lnurldevice/models.py | 2 +-
.../templates/lnurldevice/_api_docs.html | 8 +--
.../templates/lnurldevice/index.html | 66 ++++++++++++++++++-
lnbits/extensions/lnurldevice/views.py | 32 ++++++++-
5 files changed, 102 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py
index dc4456b4d..5452e3562 100644
--- a/lnbits/extensions/lnurldevice/__init__.py
+++ b/lnbits/extensions/lnurldevice/__init__.py
@@ -1,3 +1,4 @@
+import asyncio
from fastapi import APIRouter
from lnbits.db import Database
diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py
index cf6171419..0714c656d 100644
--- a/lnbits/extensions/lnurldevice/models.py
+++ b/lnbits/extensions/lnurldevice/models.py
@@ -36,7 +36,7 @@ class lnurldevices(BaseModel):
def lnurl(self, req: Request) -> Lnurl:
url = req.url_for(
- "lnurldevice.lnurl_response", device_id=self.id, _external=True
+ "lnurldevice.lnurl_v1_params", device_id=self.id
)
return lnurl_encode(url)
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
index 7f9afa27b..7497714e1 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
@@ -1,10 +1,10 @@
- Register LNURLDevice devices to receive payments in your LNbits wallet.
- Build your own here
- https://github.com/arcbtc/bitcoinpos
+ Such as the LNPoS
+ https://lnbits.github.io/lnpos
Created by, Ben Arc
+
LNURLDevice Settings
+
+
+ bitcoinSwitch embeddable QRCode
+
+
LNURLDevice device string
+
{% raw
+ %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% endraw
+ %} Click to copy URL
+
+ Click to copy URL
-
+
@@ -191,6 +221,7 @@
label="Type of device"
>
+
+
+
+
{
diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py
index 4a28a5c1a..a64de84b8 100644
--- a/lnbits/extensions/lnurldevice/views.py
+++ b/lnbits/extensions/lnurldevice/views.py
@@ -1,4 +1,6 @@
from http import HTTPStatus
+import pyqrcode
+from io import BytesIO
from fastapi import Request
from fastapi.param_functions import Query
@@ -14,6 +16,9 @@ from lnbits.decorators import check_user_exists
from . import lnurldevice_ext, lnurldevice_renderer
from .crud import get_lnurldevice, get_lnurldevicepayment
+from fastapi import Request, WebSocket, WebSocketDisconnect
+
+from starlette.responses import HTMLResponse, StreamingResponse
templates = Jinja2Templates(directory="templates")
@@ -52,6 +57,30 @@ async def displaypin(request: Request, paymentid: str = Query(None)):
{"request": request, "pin": "filler", "not_paid": True},
)
+@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse)
+async def img(request: Request, lnurldevice_id):
+ lnurldevice = await get_lnurldevice(lnurldevice_id)
+ if not lnurldevice:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist."
+ )
+ qr = pyqrcode.create(lnurldevice.lnurl(request))
+ stream = BytesIO()
+ qr.svg(stream, scale=3)
+ stream.seek(0)
+
+ async def _generator(stream: BytesIO):
+ yield stream.getvalue()
+
+ return StreamingResponse(
+ _generator(stream),
+ headers={
+ "Content-Type": "image/svg+xml",
+ "Cache-Control": "no-cache, no-store, must-revalidate",
+ "Pragma": "no-cache",
+ "Expires": "0",
+ },
+ )
##################WEBSOCKET ROUTES########################
@@ -95,4 +124,5 @@ async def updater(lnurldevice_id):
lnurldevice = await get_lnurldevice(lnurldevice_id)
if not lnurldevice:
return
- await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
\ No newline at end of file
+ await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
+
From 3bf38884ed8cb30ef9b5c428fdaea1acbedf1020 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 7 Oct 2022 08:30:07 +0200
Subject: [PATCH 021/134] log successful connection to backend with
logger.success()
---
lnbits/app.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 514825380..8b9cf7985 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -126,7 +126,7 @@ def check_funding_source(app: FastAPI) -> None:
logger.info("Retrying connection to backend in 5 seconds...")
await asyncio.sleep(5)
signal.signal(signal.SIGINT, original_sigint_handler)
- logger.info(
+ logger.success(
f"✔️ Backend {WALLET.__class__.__name__} connected and with a balance of {balance} msat."
)
From 52dc528a8a7557da685da624e22058e737e49a16 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 7 Oct 2022 11:29:06 +0100
Subject: [PATCH 022/134] Added qrdialogue
---
.../templates/lnurldevice/index.html | 61 +++++++++++++++----
lnbits/extensions/lnurldevice/views.py | 19 +-----
lnbits/extensions/lnurldevice/views_api.py | 13 +++-
3 files changed, 61 insertions(+), 32 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
index 528e5afe6..b55c83f76 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
@@ -94,18 +94,17 @@
- bitcoinSwitch embeddable QRCode
-
+ v-if="props.row.device == 'switch'"
+ :disable="protocol == 'http:'"
+ flat
+ unelevated
+ dense
+ size="xs"
+ icon="visibility"
+ :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
+ @click="openQrCodeDialog(props.row.id)"
+ > LNURLs only work over HTTPS view LNURL
+
+
+
+
+
+ {% raw %}
+
+
+ ID: {{ qrCodeDialog.data.id }}
+
+
+ {% endraw %}
+
+ Copy LNURL
+ Close
+
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}
@@ -306,6 +333,7 @@
mixins: [windowMixin],
data: function () {
return {
+ protocol: window.location.protocol,
location: window.location.hostname,
wslocation: window.location.hostname,
filter: '',
@@ -404,6 +432,14 @@
}
},
methods: {
+ openQrCodeDialog: function (lnurldevice_id) {
+ var lnurldevice = _.findWhere(this.lnurldeviceLinks, {id: lnurldevice_id})
+ console.log(lnurldevice)
+ this.qrCodeDialog.data = _.clone(lnurldevice)
+ this.qrCodeDialog.data.url =
+ window.location.protocol + '//' + window.location.host
+ this.qrCodeDialog.show = true
+ },
cancellnurldevice: function (data) {
var self = this
self.formDialoglnurldevice.show = false
@@ -460,6 +496,7 @@
.then(function (response) {
if (response.data) {
self.lnurldeviceLinks = response.data.map(maplnurldevice)
+ console.log(response.data)
}
})
.catch(function (error) {
diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py
index a64de84b8..26f581d36 100644
--- a/lnbits/extensions/lnurldevice/views.py
+++ b/lnbits/extensions/lnurldevice/views.py
@@ -64,23 +64,8 @@ async def img(request: Request, lnurldevice_id):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist."
)
- qr = pyqrcode.create(lnurldevice.lnurl(request))
- stream = BytesIO()
- qr.svg(stream, scale=3)
- stream.seek(0)
-
- async def _generator(stream: BytesIO):
- yield stream.getvalue()
-
- return StreamingResponse(
- _generator(stream),
- headers={
- "Content-Type": "image/svg+xml",
- "Cache-Control": "no-cache, no-store, must-revalidate",
- "Pragma": "no-cache",
- "Expires": "0",
- },
- )
+ return lnurldevice.lnurl(request)
+
##################WEBSOCKET ROUTES########################
diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py
index d152d210c..4a4c2b905 100644
--- a/lnbits/extensions/lnurldevice/views_api.py
+++ b/lnbits/extensions/lnurldevice/views_api.py
@@ -45,14 +45,21 @@ async def api_lnurldevice_create_or_update(
@lnurldevice_ext.get("/api/v1/lnurlpos")
-async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_lnurldevices_retrieve(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)):
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
try:
return [
- {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
+ {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
+ for lnurldevice in await get_lnurldevices(wallet_ids)
]
except:
- return ""
+ try:
+ return [
+ {**lnurldevice.dict()}
+ for lnurldevice in await get_lnurldevices(wallet_ids)
+ ]
+ except:
+ return ""
@lnurldevice_ext.get("/api/v1/lnurlpos/{lnurldevice_id}")
From 7711387d6472171539493c2f830869edef1e49fd Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 15:23:57 +0300
Subject: [PATCH 023/134] fix: use bigint for amounts in postgres (#1030)
---
lnbits/db.py | 6 ++++++
lnbits/extensions/boltcards/migrations.py | 8 ++++----
lnbits/extensions/boltz/migrations.py | 12 ++++++------
lnbits/extensions/invoices/migrations.py | 2 +-
lnbits/extensions/lnurldevice/migrations.py | 2 +-
lnbits/extensions/lnurlpayout/migrations.py | 4 ++--
lnbits/extensions/streamalerts/migrations.py | 2 +-
lnbits/extensions/tipjar/migrations.py | 4 ++--
8 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/lnbits/db.py b/lnbits/db.py
index 669817842..f52b03914 100644
--- a/lnbits/db.py
+++ b/lnbits/db.py
@@ -52,6 +52,12 @@ class Compat:
return ""
return ""
+ @property
+ def big_int(self) -> str:
+ if self.type in {POSTGRES}:
+ return "BIGINT"
+ return "INT"
+
class Connection(Compat):
def __init__(self, conn: AsyncConnection, txn, typ, name, schema):
diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py
index 081260139..9609e0c37 100644
--- a/lnbits/extensions/boltcards/migrations.py
+++ b/lnbits/extensions/boltcards/migrations.py
@@ -29,7 +29,7 @@ async def m001_initial(db):
)
await db.execute(
- """
+ f"""
CREATE TABLE boltcards.hits (
id TEXT PRIMARY KEY UNIQUE,
card_id TEXT NOT NULL,
@@ -38,7 +38,7 @@ async def m001_initial(db):
useragent TEXT,
old_ctr INT NOT NULL DEFAULT 0,
new_ctr INT NOT NULL DEFAULT 0,
- amount INT NOT NULL,
+ amount {db.big_int} NOT NULL,
time TIMESTAMP NOT NULL DEFAULT """
+ db.timestamp_now
+ """
@@ -47,11 +47,11 @@ async def m001_initial(db):
)
await db.execute(
- """
+ f"""
CREATE TABLE boltcards.refunds (
id TEXT PRIMARY KEY UNIQUE,
hit_id TEXT NOT NULL,
- refund_amount INT NOT NULL,
+ refund_amount {db.big_int} NOT NULL,
time TIMESTAMP NOT NULL DEFAULT """
+ db.timestamp_now
+ """
diff --git a/lnbits/extensions/boltz/migrations.py b/lnbits/extensions/boltz/migrations.py
index e4026dd04..925322ecd 100644
--- a/lnbits/extensions/boltz/migrations.py
+++ b/lnbits/extensions/boltz/migrations.py
@@ -1,16 +1,16 @@
async def m001_initial(db):
await db.execute(
- """
+ f"""
CREATE TABLE boltz.submarineswap (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
payment_hash TEXT NOT NULL,
- amount INT NOT NULL,
+ amount {db.big_int} NOT NULL,
status TEXT NOT NULL,
boltz_id TEXT NOT NULL,
refund_address TEXT NOT NULL,
refund_privkey TEXT NOT NULL,
- expected_amount INT NOT NULL,
+ expected_amount {db.big_int} NOT NULL,
timeout_block_height INT NOT NULL,
address TEXT NOT NULL,
bip21 TEXT NOT NULL,
@@ -22,12 +22,12 @@ async def m001_initial(db):
"""
)
await db.execute(
- """
+ f"""
CREATE TABLE boltz.reverse_submarineswap (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
onchain_address TEXT NOT NULL,
- amount INT NOT NULL,
+ amount {db.big_int} NOT NULL,
instant_settlement BOOLEAN NOT NULL,
status TEXT NOT NULL,
boltz_id TEXT NOT NULL,
@@ -37,7 +37,7 @@ async def m001_initial(db):
claim_privkey TEXT NOT NULL,
lockup_address TEXT NOT NULL,
invoice TEXT NOT NULL,
- onchain_amount INT NOT NULL,
+ onchain_amount {db.big_int} NOT NULL,
time TIMESTAMP NOT NULL DEFAULT """
+ db.timestamp_now
+ """
diff --git a/lnbits/extensions/invoices/migrations.py b/lnbits/extensions/invoices/migrations.py
index c47a954ae..74a0fdbad 100644
--- a/lnbits/extensions/invoices/migrations.py
+++ b/lnbits/extensions/invoices/migrations.py
@@ -45,7 +45,7 @@ async def m001_initial_invoices(db):
id TEXT PRIMARY KEY,
invoice_id TEXT NOT NULL,
- amount INT NOT NULL,
+ amount {db.big_int} NOT NULL,
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py
index c7899282c..da62cb7e6 100644
--- a/lnbits/extensions/lnurldevice/migrations.py
+++ b/lnbits/extensions/lnurldevice/migrations.py
@@ -29,7 +29,7 @@ async def m001_initial(db):
payhash TEXT,
payload TEXT NOT NULL,
pin INT,
- sats INT,
+ sats {db.big_int},
timestamp TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
);
"""
diff --git a/lnbits/extensions/lnurlpayout/migrations.py b/lnbits/extensions/lnurlpayout/migrations.py
index 6af047910..7a45e4953 100644
--- a/lnbits/extensions/lnurlpayout/migrations.py
+++ b/lnbits/extensions/lnurlpayout/migrations.py
@@ -3,14 +3,14 @@ async def m001_initial(db):
Initial lnurlpayouts table.
"""
await db.execute(
- """
+ f"""
CREATE TABLE lnurlpayout.lnurlpayouts (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
wallet TEXT NOT NULL,
admin_key TEXT NOT NULL,
lnurlpay TEXT NOT NULL,
- threshold INT NOT NULL
+ threshold {db.big_int} NOT NULL
);
"""
)
diff --git a/lnbits/extensions/streamalerts/migrations.py b/lnbits/extensions/streamalerts/migrations.py
index 1b0cea375..7d50e8f11 100644
--- a/lnbits/extensions/streamalerts/migrations.py
+++ b/lnbits/extensions/streamalerts/migrations.py
@@ -25,7 +25,7 @@ async def m001_initial(db):
name TEXT NOT NULL,
message TEXT NOT NULL,
cur_code TEXT NOT NULL,
- sats INT NOT NULL,
+ sats {db.big_int} NOT NULL,
amount FLOAT NOT NULL,
service INTEGER NOT NULL,
posted BOOLEAN NOT NULL,
diff --git a/lnbits/extensions/tipjar/migrations.py b/lnbits/extensions/tipjar/migrations.py
index 6b58fbca2..d8f6da3f0 100644
--- a/lnbits/extensions/tipjar/migrations.py
+++ b/lnbits/extensions/tipjar/migrations.py
@@ -19,8 +19,8 @@ async def m001_initial(db):
wallet TEXT NOT NULL,
name TEXT NOT NULL,
message TEXT NOT NULL,
- sats INT NOT NULL,
- tipjar INT NOT NULL,
+ sats {db.big_int} NOT NULL,
+ tipjar {db.big_int} NOT NULL,
FOREIGN KEY(tipjar) REFERENCES {db.references_schema}TipJars(id)
);
"""
From 8d4337679f93161966310ccf56d30eb4fd576317 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 7 Oct 2022 13:46:40 +0100
Subject: [PATCH 024/134] Websocket/listener working
---
lnbits/extensions/lnurldevice/crud.py | 6 +++--
lnbits/extensions/lnurldevice/lnurl.py | 26 +++++++++++--------
lnbits/extensions/lnurldevice/tasks.py | 8 +++---
.../templates/lnurldevice/index.html | 3 ++-
lnbits/extensions/lnurldevice/views_api.py | 9 ++++---
5 files changed, 30 insertions(+), 22 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/crud.py b/lnbits/extensions/lnurldevice/crud.py
index 451665212..4c25e4cb4 100644
--- a/lnbits/extensions/lnurldevice/crud.py
+++ b/lnbits/extensions/lnurldevice/crud.py
@@ -22,9 +22,10 @@ async def create_lnurldevice(
wallet,
currency,
device,
- profit
+ profit,
+ amount
)
- VALUES (?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
lnurldevice_id,
@@ -34,6 +35,7 @@ async def create_lnurldevice(
data.currency,
data.device,
data.profit,
+ data.amount,
),
)
return await get_lnurldevice(lnurldevice_id)
diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py
index a2bc0dd4f..3823005ea 100644
--- a/lnbits/extensions/lnurldevice/lnurl.py
+++ b/lnbits/extensions/lnurldevice/lnurl.py
@@ -103,9 +103,19 @@ async def lnurl_v1_params(
if paymentcheck:
return {"status": "ERROR", "reason": f"Payment already claimed"}
if device.device == "switch":
+
+ price_msat = (
+ await fiat_amount_as_satoshis(float(device.profit), device.currency)
+ if device.currency != "sat"
+ else amount_in_cent
+ ) * 1000
+
lnurldevicepayment = await create_lnurldevicepayment(
deviceid=device.id,
- sats=device.profit,
+ payload="bla",
+ sats=price_msat,
+ pin=1,
+ payhash="bla",
)
if not lnurldevicepayment:
return {"status": "ERROR", "reason": "Could not create payment."}
@@ -114,8 +124,8 @@ async def lnurl_v1_params(
"callback": request.url_for(
"lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id
),
- "minSendable": device.profit * 1000,
- "maxSendable": device.profit * 1000,
+ "minSendable": price_msat,
+ "maxSendable": price_msat,
"metadata": await device.lnurlpay_metadata(),
}
if len(p) % 4 > 0:
@@ -224,21 +234,15 @@ async def lnurl_callback(
payment_hash, payment_request = await create_invoice(
wallet_id=device.wallet,
amount=lnurldevicepayment.sats / 1000,
- memo=device.title,
+ memo=device.title + "-" + lnurldevicepayment.id,
unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
- extra={"tag": "Switch", "id": device.paymentid, "time": device.amount},
+ extra={"tag": "Switch", "id": paymentid, "time": device.amount},
)
lnurldevicepayment = await update_lnurldevicepayment(
lnurldevicepayment_id=paymentid, payhash=payment_hash
)
-
return {
"pr": payment_request,
- "successAction": {
- "tag": "url",
- "description": "Check the attached link",
- "url": request.url_for("lnurldevice.displaypin", paymentid=paymentid),
- },
"routes": [],
}
diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py
index 2337c0ad1..4bf0d3b90 100644
--- a/lnbits/extensions/lnurldevice/tasks.py
+++ b/lnbits/extensions/lnurldevice/tasks.py
@@ -12,8 +12,9 @@ from lnbits.core.services import pay_invoice
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
-from .crud import get_lnurldevice
+from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment
from .views import updater
+from loguru import logger
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
@@ -23,17 +24,16 @@ async def wait_for_paid_invoices():
payment = await invoice_queue.get()
await on_invoice_paid(payment)
-
async def on_invoice_paid(payment: Payment) -> None:
# (avoid loops)
- if "switch" == payment.extra.get("tag"):
+ if "Switch" == payment.extra.get("tag"):
lnurldevicepayment = await get_lnurldevicepayment(payment.extra.get("id"))
if not lnurldevicepayment:
return
if lnurldevicepayment.payhash == "used":
return
lnurldevicepayment = await update_lnurldevicepayment(
- lnurldevicepayment_id=paymentid, payhash="used"
+ lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
)
return await updater(lnurldevicepayment.deviceid)
return
\ No newline at end of file
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
index b55c83f76..ccb0a6e94 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
@@ -240,6 +240,7 @@
fill-mask="0"
reverse-fill-mask
:step="'0.01'"
+ value="0.00"
>
Date: Fri, 7 Oct 2022 23:18:57 +0100
Subject: [PATCH 025/134] Black
---
lnbits/extensions/lnurldevice/__init__.py | 2 ++
lnbits/extensions/lnurldevice/lnurl.py | 2 +-
lnbits/extensions/lnurldevice/migrations.py | 5 ++++-
lnbits/extensions/lnurldevice/models.py | 4 +---
lnbits/extensions/lnurldevice/tasks.py | 10 ++++++----
lnbits/extensions/lnurldevice/views.py | 4 ++--
lnbits/extensions/lnurldevice/views_api.py | 12 +++++++-----
7 files changed, 23 insertions(+), 16 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py
index 5452e3562..d987e6cc1 100644
--- a/lnbits/extensions/lnurldevice/__init__.py
+++ b/lnbits/extensions/lnurldevice/__init__.py
@@ -13,11 +13,13 @@ lnurldevice_ext: APIRouter = APIRouter(prefix="/lnurldevice", tags=["lnurldevice
def lnurldevice_renderer():
return template_renderer(["lnbits/extensions/lnurldevice/templates"])
+
from .tasks import wait_for_paid_invoices
from .lnurl import * # noqa
from .views import * # noqa
from .views_api import * # noqa
+
def lnurldevice_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/lnurldevice/lnurl.py b/lnbits/extensions/lnurldevice/lnurl.py
index 3823005ea..79892b78a 100644
--- a/lnbits/extensions/lnurldevice/lnurl.py
+++ b/lnbits/extensions/lnurldevice/lnurl.py
@@ -122,7 +122,7 @@ async def lnurl_v1_params(
return {
"tag": "payRequest",
"callback": request.url_for(
- "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id
+ "lnurldevice.lnurl_callback", paymentid=lnurldevicepayment.id
),
"minSendable": price_msat,
"maxSendable": price_msat,
diff --git a/lnbits/extensions/lnurldevice/migrations.py b/lnbits/extensions/lnurldevice/migrations.py
index 65913b023..fa56b3d13 100644
--- a/lnbits/extensions/lnurldevice/migrations.py
+++ b/lnbits/extensions/lnurldevice/migrations.py
@@ -80,8 +80,11 @@ async def m002_redux(db):
except:
return
+
async def m003_redux(db):
"""
Add 'meta' for storing various metadata about the wallet
"""
- await db.execute("ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;")
\ No newline at end of file
+ await db.execute(
+ "ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;"
+ )
diff --git a/lnbits/extensions/lnurldevice/models.py b/lnbits/extensions/lnurldevice/models.py
index 0714c656d..01bcc2ba6 100644
--- a/lnbits/extensions/lnurldevice/models.py
+++ b/lnbits/extensions/lnurldevice/models.py
@@ -35,9 +35,7 @@ class lnurldevices(BaseModel):
return cls(**dict(row))
def lnurl(self, req: Request) -> Lnurl:
- url = req.url_for(
- "lnurldevice.lnurl_v1_params", device_id=self.id
- )
+ url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
return lnurl_encode(url)
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py
index 4bf0d3b90..2f048138a 100644
--- a/lnbits/extensions/lnurldevice/tasks.py
+++ b/lnbits/extensions/lnurldevice/tasks.py
@@ -16,6 +16,7 @@ from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepay
from .views import updater
from loguru import logger
+
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue, get_current_extension_name())
@@ -23,7 +24,8 @@ async def wait_for_paid_invoices():
while True:
payment = await invoice_queue.get()
await on_invoice_paid(payment)
-
+
+
async def on_invoice_paid(payment: Payment) -> None:
# (avoid loops)
if "Switch" == payment.extra.get("tag"):
@@ -33,7 +35,7 @@ async def on_invoice_paid(payment: Payment) -> None:
if lnurldevicepayment.payhash == "used":
return
lnurldevicepayment = await update_lnurldevicepayment(
- lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
- )
+ lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
+ )
return await updater(lnurldevicepayment.deviceid)
- return
\ No newline at end of file
+ return
diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py
index 26f581d36..752d2c81b 100644
--- a/lnbits/extensions/lnurldevice/views.py
+++ b/lnbits/extensions/lnurldevice/views.py
@@ -57,6 +57,7 @@ async def displaypin(request: Request, paymentid: str = Query(None)):
{"request": request, "pin": "filler", "not_paid": True},
)
+
@lnurldevice_ext.get("/img/{lnurldevice_id}", response_class=StreamingResponse)
async def img(request: Request, lnurldevice_id):
lnurldevice = await get_lnurldevice(lnurldevice_id)
@@ -65,7 +66,7 @@ async def img(request: Request, lnurldevice_id):
status_code=HTTPStatus.NOT_FOUND, detail="LNURLDevice does not exist."
)
return lnurldevice.lnurl(request)
-
+
##################WEBSOCKET ROUTES########################
@@ -110,4 +111,3 @@ async def updater(lnurldevice_id):
if not lnurldevice:
return
await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
-
diff --git a/lnbits/extensions/lnurldevice/views_api.py b/lnbits/extensions/lnurldevice/views_api.py
index 65625cbb9..c034f66ed 100644
--- a/lnbits/extensions/lnurldevice/views_api.py
+++ b/lnbits/extensions/lnurldevice/views_api.py
@@ -46,18 +46,20 @@ async def api_lnurldevice_create_or_update(
@lnurldevice_ext.get("/api/v1/lnurlpos")
-async def api_lnurldevices_retrieve(req: Request, wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_lnurldevices_retrieve(
+ req: Request, wallet: WalletTypeInfo = Depends(get_key_type)
+):
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
try:
return [
- {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
- for lnurldevice in await get_lnurldevices(wallet_ids)
+ {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
+ for lnurldevice in await get_lnurldevices(wallet_ids)
]
except:
try:
return [
- {**lnurldevice.dict()}
- for lnurldevice in await get_lnurldevices(wallet_ids)
+ {**lnurldevice.dict()}
+ for lnurldevice in await get_lnurldevices(wallet_ids)
]
except:
return ""
From d4a5fe27768a8d05417e7ed8d6340b2420291a5f Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 7 Oct 2022 23:48:42 +0100
Subject: [PATCH 026/134] isort
---
lnbits/extensions/lnurldevice/tasks.py | 4 ++--
lnbits/extensions/lnurldevice/views.py | 9 +++------
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py
index 2f048138a..db8d87f03 100644
--- a/lnbits/extensions/lnurldevice/tasks.py
+++ b/lnbits/extensions/lnurldevice/tasks.py
@@ -12,9 +12,9 @@ from lnbits.core.services import pay_invoice
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
-from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment
+from .crud import (get_lnurldevice, get_lnurldevicepayment,
+ update_lnurldevicepayment)
from .views import updater
-from loguru import logger
async def wait_for_paid_invoices():
diff --git a/lnbits/extensions/lnurldevice/views.py b/lnbits/extensions/lnurldevice/views.py
index 752d2c81b..5c6eba24b 100644
--- a/lnbits/extensions/lnurldevice/views.py
+++ b/lnbits/extensions/lnurldevice/views.py
@@ -1,13 +1,13 @@
from http import HTTPStatus
-import pyqrcode
from io import BytesIO
-from fastapi import Request
+import pyqrcode
+from fastapi import Request, WebSocket, WebSocketDisconnect
from fastapi.param_functions import Query
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
-from starlette.responses import HTMLResponse
+from starlette.responses import HTMLResponse, StreamingResponse
from lnbits.core.crud import update_payment_status
from lnbits.core.models import User
@@ -16,9 +16,6 @@ from lnbits.decorators import check_user_exists
from . import lnurldevice_ext, lnurldevice_renderer
from .crud import get_lnurldevice, get_lnurldevicepayment
-from fastapi import Request, WebSocket, WebSocketDisconnect
-
-from starlette.responses import HTMLResponse, StreamingResponse
templates = Jinja2Templates(directory="templates")
From e3bd38fc6b11cbc9c1382fbac5fdfc005a0142db Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 7 Oct 2022 23:51:28 +0100
Subject: [PATCH 027/134] back
---
lnbits/extensions/lnurldevice/tasks.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py
index db8d87f03..c8f3db04f 100644
--- a/lnbits/extensions/lnurldevice/tasks.py
+++ b/lnbits/extensions/lnurldevice/tasks.py
@@ -12,8 +12,7 @@ from lnbits.core.services import pay_invoice
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
-from .crud import (get_lnurldevice, get_lnurldevicepayment,
- update_lnurldevicepayment)
+from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment
from .views import updater
From 34effe3ae5690ad3b68d0a1af34ccc2d9d9b6e21 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 7 Oct 2022 23:57:28 +0100
Subject: [PATCH 028/134] isort
---
lnbits/extensions/lnurldevice/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py
index d987e6cc1..c27c9e17d 100644
--- a/lnbits/extensions/lnurldevice/__init__.py
+++ b/lnbits/extensions/lnurldevice/__init__.py
@@ -2,8 +2,8 @@ import asyncio
from fastapi import APIRouter
from lnbits.db import Database
-from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
+from lnbits.helpers import template_renderer
db = Database("ext_lnurldevice")
From fbc20b3579169b44227cc8ba8e20cc78be1b65f5 Mon Sep 17 00:00:00 2001
From: ben
Date: Sat, 8 Oct 2022 01:28:46 +0100
Subject: [PATCH 029/134] added credits
---
.../templates/lnurldevice/_api_docs.html | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
index 7497714e1..b239e9812 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
@@ -2,12 +2,21 @@
For LNURL based Points of Sale, ATMs, and relay devices
- Such as the LNPoS
+ Use with:
+ LNPoS
https://lnbits.github.io/lnpos
+ bitcoinSwitch
+ https://github.com/lnbits/bitcoinSwitch
+ FOSSA
+ https://github.com/lnbits/fossa
- Created by, Ben ArcBen Arc
, BC, Vlad Stan
From 3bf3c91007ae4f589f06176a405061b92878e5ff Mon Sep 17 00:00:00 2001
From: calle <93376500+callebtc@users.noreply.github.com>
Date: Mon, 10 Oct 2022 16:12:06 +0200
Subject: [PATCH 030/134] Add backend: ln.tips (LightningTipBot) wallet (#921)
* add ln.tips
* add wallet
* revert lnbits
* make format
* .env.example
* listener fix
* reconnect fixed
* make format
* make format
---
.env.example | 7 +-
lnbits/extensions/lnurldevice/__init__.py | 5 +-
.../templates/lnurldevice/_api_docs.html | 22 +--
.../templates/lnurldevice/index.html | 142 ++++++++-------
lnbits/wallets/__init__.py | 2 +
lnbits/wallets/lntips.py | 170 ++++++++++++++++++
6 files changed, 263 insertions(+), 85 deletions(-)
create mode 100644 lnbits/wallets/lntips.py
diff --git a/.env.example b/.env.example
index 7d6de35f4..987c6ca69 100644
--- a/.env.example
+++ b/.env.example
@@ -41,7 +41,7 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador"
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
-# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet
+# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
@@ -92,3 +92,8 @@ LNBITS_DENOMINATION=sats
# EclairWallet
ECLAIR_URL=http://127.0.0.1:8283
ECLAIR_PASS=eclairpw
+
+# LnTipsWallet
+# Enter /api in LightningTipBot to get your key
+LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
+LNTIPS_API_ENDPOINT=https://ln.tips
diff --git a/lnbits/extensions/lnurldevice/__init__.py b/lnbits/extensions/lnurldevice/__init__.py
index c27c9e17d..d2010c449 100644
--- a/lnbits/extensions/lnurldevice/__init__.py
+++ b/lnbits/extensions/lnurldevice/__init__.py
@@ -1,9 +1,10 @@
import asyncio
+
from fastapi import APIRouter
from lnbits.db import Database
-from lnbits.tasks import catch_everything_and_restart
from lnbits.helpers import template_renderer
+from lnbits.tasks import catch_everything_and_restart
db = Database("ext_lnurldevice")
@@ -14,8 +15,8 @@ def lnurldevice_renderer():
return template_renderer(["lnbits/extensions/lnurldevice/templates"])
-from .tasks import wait_for_paid_invoices
from .lnurl import * # noqa
+from .tasks import wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
index b239e9812..f93d44d80 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/_api_docs.html
@@ -3,20 +3,22 @@
For LNURL based Points of Sale, ATMs, and relay devices
Use with:
- LNPoS
- https://lnbits.github.io/lnpos
+ https://lnbits.github.io/lnpos
- bitcoinSwitch
- https://github.com/lnbits/bitcoinSwitch
+ https://github.com/lnbits/bitcoinSwitch
- FOSSA
- https://github.com/lnbits/fossa
+ https://github.com/lnbits/fossa
- Created by, Ben Arc, BC, Vlad StanBen Arc,
+ BC,
+ Vlad Stan
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
index ccb0a6e94..028dd94b4 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
@@ -94,17 +94,19 @@
LNURLs only work over HTTPS view LNURL
+ v-if="props.row.device == 'switch'"
+ :disable="protocol == 'http:'"
+ flat
+ unelevated
+ dense
+ size="xs"
+ icon="visibility"
+ :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
+ @click="openQrCodeDialog(props.row.id)"
+ >
+ LNURLs only work over HTTPS view LNURL
LNURLDevice device string
- {% raw
- %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% endraw
- %} Click to copy URL
-
- {% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{%
+ endraw %} Click to copy URL
+
+ {% raw
- %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
- {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
- %} Click to copy URL
-
-
+ >{% raw
+ %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
+ {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
+ %} Click to copy URL
+
+
@@ -229,29 +230,28 @@
label="Profit margin (% added to invoices/deducted from faucets)"
>
-
-
-
+
+
+
ID: {{ qrCodeDialog.data.id }}
-
{% endraw %}
@@ -434,13 +433,15 @@
},
methods: {
openQrCodeDialog: function (lnurldevice_id) {
- var lnurldevice = _.findWhere(this.lnurldeviceLinks, {id: lnurldevice_id})
- console.log(lnurldevice)
- this.qrCodeDialog.data = _.clone(lnurldevice)
- this.qrCodeDialog.data.url =
- window.location.protocol + '//' + window.location.host
- this.qrCodeDialog.show = true
- },
+ var lnurldevice = _.findWhere(this.lnurldeviceLinks, {
+ id: lnurldevice_id
+ })
+ console.log(lnurldevice)
+ this.qrCodeDialog.data = _.clone(lnurldevice)
+ this.qrCodeDialog.data.url =
+ window.location.protocol + '//' + window.location.host
+ this.qrCodeDialog.show = true
+ },
cancellnurldevice: function (data) {
var self = this
self.formDialoglnurldevice.show = false
@@ -617,10 +618,7 @@
'//',
window.location.host
].join('')
- self.wslocation = [
- 'ws://',
- window.location.host
- ].join('')
+ self.wslocation = ['ws://', window.location.host].join('')
LNbits.api
.request('GET', '/api/v1/currencies')
.then(response => {
diff --git a/lnbits/wallets/__init__.py b/lnbits/wallets/__init__.py
index 41949652d..fa533566f 100644
--- a/lnbits/wallets/__init__.py
+++ b/lnbits/wallets/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa
+
from .cliche import ClicheWallet
from .cln import CoreLightningWallet # legacy .env support
from .cln import CoreLightningWallet as CLightningWallet
@@ -9,6 +10,7 @@ from .lnbits import LNbitsWallet
from .lndgrpc import LndWallet
from .lndrest import LndRestWallet
from .lnpay import LNPayWallet
+from .lntips import LnTipsWallet
from .lntxbot import LntxbotWallet
from .opennode import OpenNodeWallet
from .spark import SparkWallet
diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py
new file mode 100644
index 000000000..54220c85a
--- /dev/null
+++ b/lnbits/wallets/lntips.py
@@ -0,0 +1,170 @@
+import asyncio
+import hashlib
+import json
+import time
+from os import getenv
+from typing import AsyncGenerator, Dict, Optional
+
+import httpx
+from loguru import logger
+
+from .base import (
+ InvoiceResponse,
+ PaymentResponse,
+ PaymentStatus,
+ StatusResponse,
+ Wallet,
+)
+
+
+class LnTipsWallet(Wallet):
+ def __init__(self):
+ endpoint = getenv("LNTIPS_API_ENDPOINT")
+ self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
+
+ key = (
+ getenv("LNTIPS_API_KEY")
+ or getenv("LNTIPS_ADMIN_KEY")
+ or getenv("LNTIPS_INVOICE_KEY")
+ )
+ self.auth = {"Authorization": f"Basic {key}"}
+
+ async def status(self) -> StatusResponse:
+ async with httpx.AsyncClient() as client:
+ r = await client.get(
+ f"{self.endpoint}/api/v1/balance", headers=self.auth, timeout=40
+ )
+ try:
+ data = r.json()
+ except:
+ return StatusResponse(
+ f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'", 0
+ )
+
+ if data.get("error"):
+ return StatusResponse(data["error"], 0)
+
+ return StatusResponse(None, data["balance"] * 1000)
+
+ async def create_invoice(
+ self,
+ amount: int,
+ memo: Optional[str] = None,
+ description_hash: Optional[bytes] = None,
+ unhashed_description: Optional[bytes] = None,
+ **kwargs,
+ ) -> InvoiceResponse:
+ data: Dict = {"amount": amount}
+ if description_hash:
+ data["description_hash"] = description_hash.hex()
+ elif unhashed_description:
+ data["description_hash"] = hashlib.sha256(unhashed_description).hexdigest()
+ else:
+ data["memo"] = memo or ""
+
+ async with httpx.AsyncClient() as client:
+ r = await client.post(
+ f"{self.endpoint}/api/v1/createinvoice",
+ headers=self.auth,
+ json=data,
+ timeout=40,
+ )
+
+ if r.is_error:
+ try:
+ data = r.json()
+ error_message = data["message"]
+ except:
+ error_message = r.text
+ pass
+
+ return InvoiceResponse(False, None, None, error_message)
+
+ data = r.json()
+ return InvoiceResponse(
+ True, data["payment_hash"], data["payment_request"], None
+ )
+
+ async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
+ async with httpx.AsyncClient() as client:
+ r = await client.post(
+ f"{self.endpoint}/api/v1/payinvoice",
+ headers=self.auth,
+ json={"pay_req": bolt11},
+ timeout=None,
+ )
+ if r.is_error:
+ return PaymentResponse(False, None, 0, None, r.text)
+
+ if "error" in r.json():
+ try:
+ data = r.json()
+ error_message = data["error"]
+ except:
+ error_message = r.text
+ pass
+ return PaymentResponse(False, None, 0, None, error_message)
+
+ data = r.json()["details"]
+ checking_id = data["payment_hash"]
+ fee_msat = -data["fee"]
+ preimage = data["preimage"]
+ return PaymentResponse(True, checking_id, fee_msat, preimage, None)
+
+ async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
+ async with httpx.AsyncClient() as client:
+ r = await client.post(
+ f"{self.endpoint}/api/v1/invoicestatus/{checking_id}",
+ headers=self.auth,
+ )
+
+ if r.is_error or len(r.text) == 0:
+ return PaymentStatus(None)
+
+ data = r.json()
+ return PaymentStatus(data["paid"])
+
+ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
+ async with httpx.AsyncClient() as client:
+ r = await client.post(
+ url=f"{self.endpoint}/api/v1/paymentstatus/{checking_id}",
+ headers=self.auth,
+ )
+
+ if r.is_error:
+ return PaymentStatus(None)
+ data = r.json()
+
+ paid_to_status = {False: None, True: True}
+ return PaymentStatus(paid_to_status[data.get("paid")])
+
+ async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
+ last_connected = None
+ while True:
+ url = f"{self.endpoint}/api/v1/invoicestream"
+ try:
+ async with httpx.AsyncClient(timeout=None, headers=self.auth) as client:
+ last_connected = time.time()
+ async with client.stream("GET", url) as r:
+ async for line in r.aiter_lines():
+ try:
+ prefix = "data: "
+ if not line.startswith(prefix):
+ continue
+ data = line[len(prefix) :] # sse parsing
+ inv = json.loads(data)
+ if not inv.get("payment_hash"):
+ continue
+ except:
+ continue
+ yield inv["payment_hash"]
+ except Exception as e:
+ pass
+
+ # do not sleep if the connection was active for more than 10s
+ # since the backend is expected to drop the connection after 90s
+ if last_connected is None or time.time() - last_connected < 10:
+ logger.error(
+ f"lost connection to {self.endpoint}/api/v1/invoicestream, retrying in 5 seconds"
+ )
+ await asyncio.sleep(5)
From d1302e4868f0fe12cf6bdf7d25e82045981077c2 Mon Sep 17 00:00:00 2001
From: calle <93376500+callebtc@users.noreply.github.com>
Date: Tue, 11 Oct 2022 08:52:39 +0200
Subject: [PATCH 031/134] show progress (#987)
---
lnbits/core/crud.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py
index cbed62922..bb1ca0c1c 100644
--- a/lnbits/core/crud.py
+++ b/lnbits/core/crud.py
@@ -333,7 +333,7 @@ async def delete_expired_invoices(
"""
)
logger.debug(f"Checking expiry of {len(rows)} invoices")
- for (payment_request,) in rows:
+ for i, (payment_request,) in enumerate(rows):
try:
invoice = bolt11.decode(payment_request)
except:
@@ -343,7 +343,7 @@ async def delete_expired_invoices(
if expiration_date > datetime.datetime.utcnow():
continue
logger.debug(
- f"Deleting expired invoice: {invoice.payment_hash} (expired: {expiration_date})"
+ f"Deleting expired invoice {i}/{len(rows)}: {invoice.payment_hash} (expired: {expiration_date})"
)
await (conn or db).execute(
"""
From 42704eaf1bcf0a0ec6fc4309ea63f692f0721286 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 18 Oct 2022 08:49:27 +0200
Subject: [PATCH 032/134] change boltz logs from warn to debug (#1057)
---
lnbits/extensions/boltz/tasks.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltz/tasks.py b/lnbits/extensions/boltz/tasks.py
index ace94557b..d1ace04b8 100644
--- a/lnbits/extensions/boltz/tasks.py
+++ b/lnbits/extensions/boltz/tasks.py
@@ -57,7 +57,7 @@ async def check_for_pending_swaps():
swap_status = get_swap_status(swap)
# should only happen while development when regtest is reset
if swap_status.exists is False:
- logger.warning(f"Boltz - swap: {swap.boltz_id} does not exist.")
+ logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.")
await update_swap_status(swap.id, "failed")
continue
@@ -73,7 +73,7 @@ async def check_for_pending_swaps():
else:
if swap_status.hit_timeout:
if not swap_status.has_lockup:
- logger.warning(
+ logger.debug(
f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..."
)
await update_swap_status(swap.id, "timeout")
From 9de38f908bfa603c1cfb98cfd078e0136e815320 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 20 Oct 2022 17:40:23 +0300
Subject: [PATCH 033/134] fix: wallet not found error (#1063)
---
lnbits/extensions/satspay/crud.py | 9 ++++++++-
lnbits/extensions/satspay/helpers.py | 17 +++++++++++++++++
lnbits/extensions/satspay/models.py | 1 -
.../satspay/templates/satspay/display.html | 15 +++------------
lnbits/extensions/satspay/views.py | 5 ++---
lnbits/extensions/satspay/views_api.py | 17 +++--------------
6 files changed, 33 insertions(+), 31 deletions(-)
create mode 100644 lnbits/extensions/satspay/helpers.py
diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py
index 47d7a4a8d..23d391b7b 100644
--- a/lnbits/extensions/satspay/crud.py
+++ b/lnbits/extensions/satspay/crud.py
@@ -102,7 +102,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
charge = await get_charge(charge_id)
if not charge.paid:
if charge.onchainaddress:
- config = await get_config(charge.user)
+ config = await get_charge_config(charge_id)
try:
async with httpx.AsyncClient() as client:
r = await client.get(
@@ -122,3 +122,10 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
return await update_charge(charge_id=charge_id, balance=charge.amount)
row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
return Charges.from_row(row) if row else None
+
+
+async def get_charge_config(charge_id: str):
+ row = await db.fetchone(
+ """SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,)
+ )
+ return await get_config(row.user)
diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py
new file mode 100644
index 000000000..2d15b5578
--- /dev/null
+++ b/lnbits/extensions/satspay/helpers.py
@@ -0,0 +1,17 @@
+from .models import Charges
+
+
+def compact_charge(charge: Charges):
+ return {
+ "id": charge.id,
+ "description": charge.description,
+ "onchainaddress": charge.onchainaddress,
+ "payment_request": charge.payment_request,
+ "payment_hash": charge.payment_hash,
+ "time": charge.time,
+ "amount": charge.amount,
+ "balance": charge.balance,
+ "paid": charge.paid,
+ "timestamp": charge.timestamp,
+ "completelink": charge.completelink, # should be secret?
+ }
diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py
index e8638d5e2..daf63f429 100644
--- a/lnbits/extensions/satspay/models.py
+++ b/lnbits/extensions/satspay/models.py
@@ -19,7 +19,6 @@ class CreateCharge(BaseModel):
class Charges(BaseModel):
id: str
- user: str
description: Optional[str]
onchainwallet: Optional[str]
onchainaddress: Optional[str]
diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html
index f34ac5095..12288c809 100644
--- a/lnbits/extensions/satspay/templates/satspay/display.html
+++ b/lnbits/extensions/satspay/templates/satspay/display.html
@@ -328,7 +328,7 @@
)
},
checkBalances: async function () {
- if (!this.charge.hasStaleBalance) await this.refreshCharge()
+ if (this.charge.hasStaleBalance) return
try {
const {data} = await LNbits.api.request(
'GET',
@@ -339,18 +339,9 @@
LNbits.utils.notifyApiError(error)
}
},
- refreshCharge: async function () {
- try {
- const {data} = await LNbits.api.request(
- 'GET',
- `/satspay/api/v1/charge/${this.charge.id}`
- )
- this.charge = mapCharge(data, this.charge)
- } catch (error) {
- LNbits.utils.notifyApiError(error)
- }
- },
checkPendingOnchain: async function () {
+ if (!this.charge.onchainaddress) return
+
const {
bitcoin: {addresses: addressesAPI}
} = mempoolJS({
diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py
index 69d81dadf..b789bf8fe 100644
--- a/lnbits/extensions/satspay/views.py
+++ b/lnbits/extensions/satspay/views.py
@@ -9,10 +9,9 @@ from starlette.responses import HTMLResponse
from lnbits.core.crud import get_wallet
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
-from lnbits.extensions.watchonly.crud import get_config
from . import satspay_ext, satspay_renderer
-from .crud import get_charge
+from .crud import get_charge, get_charge_config
templates = Jinja2Templates(directory="templates")
@@ -32,7 +31,7 @@ async def display(request: Request, charge_id: str):
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
)
wallet = await get_wallet(charge.lnbitswallet)
- onchainwallet_config = await get_config(charge.user)
+ onchainwallet_config = await get_charge_config(charge_id)
inkey = wallet.inkey if wallet else None
mempool_endpoint = (
onchainwallet_config.mempool_endpoint if onchainwallet_config else None
diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py
index f94b970af..73c87e7c2 100644
--- a/lnbits/extensions/satspay/views_api.py
+++ b/lnbits/extensions/satspay/views_api.py
@@ -20,6 +20,7 @@ from .crud import (
get_charges,
update_charge,
)
+from .helpers import compact_charge
from .models import CreateCharge
#############################CHARGES##########################
@@ -123,25 +124,13 @@ async def api_charge_balance(charge_id):
try:
r = await client.post(
charge.webhook,
- json={
- "id": charge.id,
- "description": charge.description,
- "onchainaddress": charge.onchainaddress,
- "payment_request": charge.payment_request,
- "payment_hash": charge.payment_hash,
- "time": charge.time,
- "amount": charge.amount,
- "balance": charge.balance,
- "paid": charge.paid,
- "timestamp": charge.timestamp,
- "completelink": charge.completelink,
- },
+ json=compact_charge(charge),
timeout=40,
)
except AssertionError:
charge.webhook = None
return {
- **charge.dict(),
+ **compact_charge(charge),
**{"time_elapsed": charge.time_elapsed},
**{"time_left": charge.time_left},
**{"paid": charge.paid},
From daf5768d52c39cbcf24279d2c8604a2fefa1a197 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 21 Oct 2022 15:49:17 +0300
Subject: [PATCH 034/134] fix: remove user from model (#1067)
---
lnbits/extensions/watchonly/crud.py | 4 ++--
lnbits/extensions/watchonly/models.py | 1 -
lnbits/extensions/watchonly/views_api.py | 3 +--
3 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py
index de338b91e..c4a1df728 100644
--- a/lnbits/extensions/watchonly/crud.py
+++ b/lnbits/extensions/watchonly/crud.py
@@ -10,7 +10,7 @@ from .models import Address, Config, WalletAccount
##########################WALLETS####################
-async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
+async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount:
wallet_id = urlsafe_short_hash()
await db.execute(
"""
@@ -30,7 +30,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
""",
(
wallet_id,
- w.user,
+ user,
w.masterpub,
w.fingerprint,
w.title,
diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py
index 622f5ec80..d8c278ff8 100644
--- a/lnbits/extensions/watchonly/models.py
+++ b/lnbits/extensions/watchonly/models.py
@@ -14,7 +14,6 @@ class CreateWallet(BaseModel):
class WalletAccount(BaseModel):
id: str
- user: str
masterpub: str
fingerprint: str
title: str
diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py
index 750d46c98..9030b9c3d 100644
--- a/lnbits/extensions/watchonly/views_api.py
+++ b/lnbits/extensions/watchonly/views_api.py
@@ -86,7 +86,6 @@ async def api_wallet_create_or_update(
new_wallet = WalletAccount(
id="none",
- user=w.wallet.user,
masterpub=data.masterpub,
fingerprint=descriptor.keys[0].fingerprint.hex(),
type=descriptor.scriptpubkey_type(),
@@ -115,7 +114,7 @@ async def api_wallet_create_or_update(
)
)
- wallet = await create_watch_wallet(new_wallet)
+ wallet = await create_watch_wallet(w.wallet.user, new_wallet)
await api_get_addresses(wallet.id, w)
except Exception as e:
From 2d61a8854f38a61507289af3520e52ff54ccb1a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 17:35:58 +0200
Subject: [PATCH 035/134] improving on split payment handling
---
lnbits/extensions/splitpayments/models.py | 2 +-
lnbits/extensions/splitpayments/tasks.py | 72 ++++++++---------------
2 files changed, 24 insertions(+), 50 deletions(-)
diff --git a/lnbits/extensions/splitpayments/models.py b/lnbits/extensions/splitpayments/models.py
index 4b95ed18b..6338d97f4 100644
--- a/lnbits/extensions/splitpayments/models.py
+++ b/lnbits/extensions/splitpayments/models.py
@@ -14,7 +14,7 @@ class Target(BaseModel):
class TargetPutList(BaseModel):
wallet: str = Query(...)
alias: str = Query("")
- percent: float = Query(..., ge=0.01)
+ percent: float = Query(..., ge=0.01, lt=100)
class TargetPut(BaseModel):
diff --git a/lnbits/extensions/splitpayments/tasks.py b/lnbits/extensions/splitpayments/tasks.py
index cfc6c226f..53378b205 100644
--- a/lnbits/extensions/splitpayments/tasks.py
+++ b/lnbits/extensions/splitpayments/tasks.py
@@ -1,13 +1,11 @@
import asyncio
-import json
from loguru import logger
-from lnbits.core import db as core_db
-from lnbits.core.crud import create_payment
from lnbits.core.models import Payment
-from lnbits.helpers import get_current_extension_name, urlsafe_short_hash
-from lnbits.tasks import internal_invoice_queue, register_invoice_listener
+from lnbits.core.services import create_invoice, pay_invoice
+from lnbits.helpers import get_current_extension_name
+from lnbits.tasks import register_invoice_listener
from .crud import get_targets
@@ -22,60 +20,36 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
- if payment.extra.get("tag") == "splitpayments" or payment.extra.get("splitted"):
- # already splitted, ignore
+ if payment.extra.get("tag") == "splitpayments":
+ # already a splitted payment, ignore
return
- # now we make some special internal transfers (from no one to the receiver)
targets = await get_targets(payment.wallet_id)
if not targets:
return
- transfers = [
- (target.wallet, int(target.percent * payment.amount / 100))
- for target in targets
- ]
- transfers = [(wallet, amount) for wallet, amount in transfers if amount > 0]
- amount_left = payment.amount - sum([amount for _, amount in transfers])
+ total_percent = sum([target.percent for target in targets])
- if amount_left < 0:
- logger.error(
- "splitpayments failure: amount_left is negative.", payment.payment_hash
- )
+ if total_percent > 100:
+ logger.error("splitpayment failure: total percent adds up to more than 100%")
return
- # mark the original payment with one extra key, "splitted"
- # (this prevents us from doing this process again and it's informative)
- # and reduce it by the amount we're going to send to the producer
- await core_db.execute(
- """
- UPDATE apipayments
- SET extra = ?, amount = ?
- WHERE hash = ?
- AND checking_id NOT LIKE 'internal_%'
- """,
- (
- json.dumps(dict(**payment.extra, splitted=True)),
- amount_left,
- payment.payment_hash,
- ),
- )
-
- # perform the internal transfer using the same payment_hash
- for wallet, amount in transfers:
- internal_checking_id = f"internal_{urlsafe_short_hash()}"
- await create_payment(
- wallet_id=wallet,
- checking_id=internal_checking_id,
- payment_request="",
- payment_hash=payment.payment_hash,
- amount=amount,
- memo=payment.memo,
- pending=False,
+ logger.debug(f"performing split payments to {len(targets)} targets")
+ for target in targets:
+ amount = int(payment.amount * target.percent / 100) # msats
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=target.wallet,
+ amount=int(amount / 1000), # sats
+ internal=True,
+ memo=f"split payment: {target.percent}% for {target.alias or target.wallet}",
extra={"tag": "splitpayments"},
)
+ logger.debug(f"created split invoice: {payment_hash}")
- # manually send this for now
- await internal_invoice_queue.put(internal_checking_id)
- return
+ checking_id = await pay_invoice(
+ payment_request=payment_request,
+ wallet_id=payment.wallet_id,
+ extra={"tag": "splitpayments"},
+ )
+ logger.debug(f"paid split invoice: {checking_id}")
From fe879ad9b964766c25f8b59a0323d009d332166d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 18:15:27 +0200
Subject: [PATCH 036/134] add tpos to pr
---
lnbits/extensions/tpos/tasks.py | 58 ++++++++++-----------------------
1 file changed, 17 insertions(+), 41 deletions(-)
diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py
index f18d16892..6369bbc7f 100644
--- a/lnbits/extensions/tpos/tasks.py
+++ b/lnbits/extensions/tpos/tasks.py
@@ -1,11 +1,11 @@
import asyncio
-import json
-from lnbits.core import db as core_db
-from lnbits.core.crud import create_payment
+from loguru import logger
+
from lnbits.core.models import Payment
-from lnbits.helpers import get_current_extension_name, urlsafe_short_hash
-from lnbits.tasks import internal_invoice_queue, register_invoice_listener
+from lnbits.core.services import create_invoice, pay_invoice
+from lnbits.helpers import get_current_extension_name
+from lnbits.tasks import register_invoice_listener
from .crud import get_tpos
@@ -20,11 +20,9 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
- if payment.extra.get("tag") == "tpos" and payment.extra.get("tipSplitted"):
- # already splitted, ignore
+ if payment.extra.get("tag") != "tpos":
return
- # now we make some special internal transfers (from no one to the receiver)
tpos = await get_tpos(payment.extra.get("tposId"))
tipAmount = payment.extra.get("tipAmount")
@@ -32,39 +30,17 @@ async def on_invoice_paid(payment: Payment) -> None:
# no tip amount
return
- tipAmount = tipAmount * 1000
- amount = payment.amount - tipAmount
-
- # mark the original payment with one extra key, "splitted"
- # (this prevents us from doing this process again and it's informative)
- # and reduce it by the amount we're going to send to the producer
- await core_db.execute(
- """
- UPDATE apipayments
- SET extra = ?, amount = ?
- WHERE hash = ?
- AND checking_id NOT LIKE 'internal_%'
- """,
- (
- json.dumps(dict(**payment.extra, tipSplitted=True)),
- amount,
- payment.payment_hash,
- ),
- )
-
- # perform the internal transfer using the same payment_hash
- internal_checking_id = f"internal_{urlsafe_short_hash()}"
- await create_payment(
+ payment_hash, payment_request = await create_invoice(
wallet_id=tpos.tip_wallet,
- checking_id=internal_checking_id,
- payment_request="",
- payment_hash=payment.payment_hash,
- amount=tipAmount,
- memo=f"Tip for {payment.memo}",
- pending=False,
- extra={"tipSplitted": True},
+ amount=int(tipAmount), # sats
+ internal=True,
+ memo=f"tpos tip",
)
+ logger.debug(f"tpos: tip invoice created: {payment_hash}")
- # manually send this for now
- await internal_invoice_queue.put(internal_checking_id)
- return
+ checking_id = await pay_invoice(
+ payment_request=payment_request,
+ wallet_id=payment.wallet_id,
+ extra={"tag": "tpos"},
+ )
+ logger.debug(f"tpos: tip invoice paid: {checking_id}")
From e1fbb8c3cd112d56bbd147565cc649f007d42287 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 18:28:39 +0200
Subject: [PATCH 037/134] fix livestream extension
---
lnbits/extensions/livestream/tasks.py | 52 ++++++++-------------------
1 file changed, 14 insertions(+), 38 deletions(-)
diff --git a/lnbits/extensions/livestream/tasks.py b/lnbits/extensions/livestream/tasks.py
index 626c698ca..d081332f5 100644
--- a/lnbits/extensions/livestream/tasks.py
+++ b/lnbits/extensions/livestream/tasks.py
@@ -4,10 +4,10 @@ import json
from loguru import logger
from lnbits.core import db as core_db
-from lnbits.core.crud import create_payment
from lnbits.core.models import Payment
-from lnbits.helpers import get_current_extension_name, urlsafe_short_hash
-from lnbits.tasks import internal_invoice_listener, register_invoice_listener
+from lnbits.core.services import create_invoice, pay_invoice
+from lnbits.helpers import get_current_extension_name
+from lnbits.tasks import register_invoice_listener
from .crud import get_livestream_by_track, get_producer, get_track
@@ -44,44 +44,20 @@ async def on_invoice_paid(payment: Payment) -> None:
# now we make a special kind of internal transfer
amount = int(payment.amount * (100 - ls.fee_pct) / 100)
- # mark the original payment with two extra keys, "shared_with" and "received"
- # (this prevents us from doing this process again and it's informative)
- # and reduce it by the amount we're going to send to the producer
- await core_db.execute(
- """
- UPDATE apipayments
- SET extra = ?, amount = ?
- WHERE hash = ?
- AND checking_id NOT LIKE 'internal_%'
- """,
- (
- json.dumps(
- dict(
- **payment.extra,
- shared_with=[producer.name, producer.id],
- received=payment.amount,
- )
- ),
- payment.amount - amount,
- payment.payment_hash,
- ),
- )
-
- # perform an internal transfer using the same payment_hash to the producer wallet
- internal_checking_id = f"internal_{urlsafe_short_hash()}"
- await create_payment(
- wallet_id=producer.wallet,
- checking_id=internal_checking_id,
- payment_request="",
- payment_hash=payment.payment_hash,
- amount=amount,
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=tpos.tip_wallet,
+ amount=amount, # sats
+ internal=True,
memo=f"Revenue from '{track.name}'.",
- pending=False,
)
+ logger.debug(f"livestream: producer invoice created: {payment_hash}")
- # manually send this for now
- # await internal_invoice_paid.send(internal_checking_id)
- await internal_invoice_listener.put(internal_checking_id)
+ checking_id = await pay_invoice(
+ payment_request=payment_request,
+ wallet_id=payment.wallet_id,
+ extra={"tag": "livestream"},
+ )
+ logger.debug(f"livestream: producer invoice paid: {checking_id}")
# so the flow is the following:
# - we receive, say, 1000 satoshis
From 3814989b3a142baaea1d010f7022882b72280f2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20Pha=CC=A3m-Bachelart?=
Date: Mon, 24 Oct 2022 11:46:32 +0200
Subject: [PATCH 038/134] Add cliche wallet (#1071)
---
docs/guide/wallets.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/docs/guide/wallets.md b/docs/guide/wallets.md
index 80fb54c04..10724f34b 100644
--- a/docs/guide/wallets.md
+++ b/docs/guide/wallets.md
@@ -79,3 +79,8 @@ For the invoice to work you must have a publicly accessible URL in your LNbits.
- `LNBITS_BACKEND_WALLET_CLASS`: **OpenNodeWallet**
- `OPENNODE_API_ENDPOINT`: https://api.opennode.com/
- `OPENNODE_KEY`: opennodeAdminApiKey
+
+
+### Cliche Wallet
+
+- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000
From fc1a87f617a60de29552c8196a7f08050bb60ce5 Mon Sep 17 00:00:00 2001
From: Aaron Dewes
Date: Mon, 24 Oct 2022 11:50:52 +0200
Subject: [PATCH 039/134] Remove unused proxy_fix (#1007)
---
lnbits/app.py | 1 -
lnbits/proxy_fix.py | 95 ---------------------------------------------
2 files changed, 96 deletions(-)
delete mode 100644 lnbits/proxy_fix.py
diff --git a/lnbits/app.py b/lnbits/app.py
index 8b9cf7985..075828ef0 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -91,7 +91,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
)
app.add_middleware(GZipMiddleware, minimum_size=1000)
- # app.add_middleware(ASGIProxyFix)
check_funding_source(app)
register_assets(app)
diff --git a/lnbits/proxy_fix.py b/lnbits/proxy_fix.py
deleted file mode 100644
index 897835e0d..000000000
--- a/lnbits/proxy_fix.py
+++ /dev/null
@@ -1,95 +0,0 @@
-from functools import partial
-from typing import Callable, List, Optional
-from urllib.parse import urlparse
-from urllib.request import parse_http_list as _parse_list_header
-
-from quart import Request
-from quart_trio.asgi import TrioASGIHTTPConnection
-from werkzeug.datastructures import Headers
-
-
-class ASGIProxyFix(TrioASGIHTTPConnection):
- def _create_request_from_scope(self, send: Callable) -> Request:
- headers = Headers()
- headers["Remote-Addr"] = (self.scope.get("client") or [""])[0]
- for name, value in self.scope["headers"]:
- headers.add(name.decode("latin1").title(), value.decode("latin1"))
- if self.scope["http_version"] < "1.1":
- headers.setdefault("Host", self.app.config["SERVER_NAME"] or "")
-
- path = self.scope["path"]
- path = path if path[0] == "/" else urlparse(path).path
-
- x_proto = self._get_real_value(1, headers.get("X-Forwarded-Proto"))
- if x_proto:
- self.scope["scheme"] = x_proto
-
- x_host = self._get_real_value(1, headers.get("X-Forwarded-Host"))
- if x_host:
- headers["host"] = x_host.lower()
-
- return self.app.request_class(
- self.scope["method"],
- self.scope["scheme"],
- path,
- self.scope["query_string"],
- headers,
- self.scope.get("root_path", ""),
- self.scope["http_version"],
- max_content_length=self.app.config["MAX_CONTENT_LENGTH"],
- body_timeout=self.app.config["BODY_TIMEOUT"],
- send_push_promise=partial(self._send_push_promise, send),
- scope=self.scope,
- )
-
- def _get_real_value(self, trusted: int, value: Optional[str]) -> Optional[str]:
- """Get the real value from a list header based on the configured
- number of trusted proxies.
- :param trusted: Number of values to trust in the header.
- :param value: Comma separated list header value to parse.
- :return: The real value, or ``None`` if there are fewer values
- than the number of trusted proxies.
- .. versionchanged:: 1.0
- Renamed from ``_get_trusted_comma``.
- .. versionadded:: 0.15
- """
- if not (trusted and value):
- return None
-
- values = self.parse_list_header(value)
- if len(values) >= trusted:
- return values[-trusted]
-
- return None
-
- def parse_list_header(self, value: str) -> List[str]:
- result = []
- for item in _parse_list_header(value):
- if item[:1] == item[-1:] == '"':
- item = self.unquote_header_value(item[1:-1])
- result.append(item)
- return result
-
- def unquote_header_value(self, value: str, is_filename: bool = False) -> str:
- r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
- This does not use the real unquoting but what browsers are actually
- using for quoting.
- .. versionadded:: 0.5
- :param value: the header value to unquote.
- :param is_filename: The value represents a filename or path.
- """
- if value and value[0] == value[-1] == '"':
- # this is not the real unquoting, but fixing this so that the
- # RFC is met will result in bugs with internet explorer and
- # probably some other browsers as well. IE for example is
- # uploading files with "C:\foo\bar.txt" as filename
- value = value[1:-1]
-
- # if this is a filename and the starting characters look like
- # a UNC path, then just return the value without quotes. Using the
- # replace sequence below on a UNC path has the effect of turning
- # the leading double slash into a single slash and then
- # _fix_ie_filename() doesn't work correctly. See #458.
- if not is_filename or value[:2] != "\\\\":
- return value.replace("\\\\", "\\").replace('\\"', '"')
- return value
From 193d037ad6aa102152a824611c65a96a6ff876df Mon Sep 17 00:00:00 2001
From: Anton Kovalenko
Date: Mon, 24 Oct 2022 13:23:11 +0300
Subject: [PATCH 040/134] Allow more than 2-nd level domain in incoming
ln-addresses (#914)
---
lnbits/core/views/api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index c07df568a..983d5a261 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -476,7 +476,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
except:
# parse internet identifier (user@domain.com)
name_domain = code.split("@")
- if len(name_domain) == 2 and len(name_domain[1].split(".")) == 2:
+ if len(name_domain) == 2 and len(name_domain[1].split(".")) >= 2:
name, domain = name_domain
url = (
("http://" if domain.endswith(".onion") else "https://")
From 09cf654427d8f42baee484a5146a180b1261b1e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 24 Oct 2022 12:32:20 +0200
Subject: [PATCH 041/134] Proper Error Handling for qrcode-stream components
(#1076)
* proper errorhandling for camera
* fix javascript for calle
---
lnbits/core/static/js/wallet.js | 29 ++++++++++++++++++++++++++
lnbits/core/templates/core/wallet.html | 2 ++
2 files changed, 31 insertions(+)
diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js
index 76d82ad4c..668013135 100644
--- a/lnbits/core/static/js/wallet.js
+++ b/lnbits/core/static/js/wallet.js
@@ -361,6 +361,35 @@ new Vue({
this.receive.status = 'pending'
})
},
+ onInitQR: async function (promise) {
+ try {
+ await promise
+ } catch (error) {
+ let mapping = {
+ NotAllowedError: 'ERROR: you need to grant camera access permission',
+ NotFoundError: 'ERROR: no camera on this device',
+ NotSupportedError:
+ 'ERROR: secure context required (HTTPS, localhost)',
+ NotReadableError: 'ERROR: is the camera already in use?',
+ OverconstrainedError: 'ERROR: installed cameras are not suitable',
+ StreamApiNotSupportedError:
+ 'ERROR: Stream API is not supported in this browser',
+ InsecureContextError:
+ 'ERROR: Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP.'
+ }
+ let valid_error = Object.keys(mapping).filter(key => {
+ return error.name === key
+ })
+ let camera_error = valid_error
+ ? mapping[valid_error]
+ : `ERROR: Camera error (${error.name})`
+ this.parse.camera.show = false
+ this.$q.notify({
+ message: camera_error,
+ type: 'negative'
+ })
+ }
+ },
decodeQR: function (res) {
this.parse.data.request = res
this.decodeRequest()
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index bccdc2b48..4bf6067c0 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -653,6 +653,7 @@
@@ -671,6 +672,7 @@
From 09871bbabc27b69886d56770178b19f445b7c48f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 24 Oct 2022 16:29:30 +0200
Subject: [PATCH 042/134] fix mypy for extensions (#873)
* explicitly exclude all extensions from mypy
* fix example extension mypy
* fix subdomains extension mypy + 1 type error fixed
* fix mypy discordbot
* mypy check copilot extensnion
* copilot black
* add invoices ext to ignore
* add boltz and boltcard
* copilit id is necessary
* was discordbot is ok
Co-authored-by: dni
---
lnbits/extensions/copilot/crud.py | 8 ++---
lnbits/extensions/copilot/tasks.py | 20 ++++++------
lnbits/extensions/copilot/views.py | 8 +++--
lnbits/extensions/copilot/views_api.py | 9 +++---
lnbits/extensions/discordbot/crud.py | 6 ++--
lnbits/extensions/discordbot/views.py | 4 ++-
lnbits/extensions/discordbot/views_api.py | 39 +++++++++++++----------
lnbits/extensions/example/views.py | 5 ++-
lnbits/extensions/subdomains/crud.py | 4 +--
lnbits/extensions/subdomains/models.py | 30 ++++++++---------
lnbits/extensions/subdomains/tasks.py | 4 +--
lnbits/extensions/subdomains/views.py | 4 ++-
lnbits/extensions/subdomains/views_api.py | 25 +++++++++++----
pyproject.toml | 30 +++++++++++++++--
14 files changed, 125 insertions(+), 71 deletions(-)
diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py
index d0da044eb..5ecb5cd45 100644
--- a/lnbits/extensions/copilot/crud.py
+++ b/lnbits/extensions/copilot/crud.py
@@ -10,7 +10,7 @@ from .models import Copilots, CreateCopilotData
async def create_copilot(
data: CreateCopilotData, inkey: Optional[str] = ""
-) -> Copilots:
+) -> Optional[Copilots]:
copilot_id = urlsafe_short_hash()
await db.execute(
"""
@@ -67,19 +67,19 @@ async def create_copilot(
async def update_copilot(
- data: CreateCopilotData, copilot_id: Optional[str] = ""
+ data: CreateCopilotData, copilot_id: str
) -> Optional[Copilots]:
q = ", ".join([f"{field[0]} = ?" for field in data])
items = [f"{field[1]}" for field in data]
items.append(copilot_id)
- await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items))
+ await db.execute(f"UPDATE copilot.newer_copilots SET {q} WHERE id = ?", (items,))
row = await db.fetchone(
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
)
return Copilots(**row) if row else None
-async def get_copilot(copilot_id: str) -> Copilots:
+async def get_copilot(copilot_id: str) -> Optional[Copilots]:
row = await db.fetchone(
"SELECT * FROM copilot.newer_copilots WHERE id = ?", (copilot_id,)
)
diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py
index c59ef4cc8..48ad7813d 100644
--- a/lnbits/extensions/copilot/tasks.py
+++ b/lnbits/extensions/copilot/tasks.py
@@ -26,7 +26,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
webhook = None
data = None
- if payment.extra.get("tag") != "copilot":
+ if not payment.extra or payment.extra.get("tag") != "copilot":
# not an copilot invoice
return
@@ -71,12 +71,12 @@ async def on_invoice_paid(payment: Payment) -> None:
async def mark_webhook_sent(payment: Payment, status: int) -> None:
- payment.extra["wh_status"] = status
-
- await core_db.execute(
- """
- UPDATE apipayments SET extra = ?
- WHERE hash = ?
- """,
- (json.dumps(payment.extra), payment.payment_hash),
- )
+ if payment.extra:
+ payment.extra["wh_status"] = status
+ await core_db.execute(
+ """
+ UPDATE apipayments SET extra = ?
+ WHERE hash = ?
+ """,
+ (json.dumps(payment.extra), payment.payment_hash),
+ )
diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py
index 7ee7f590e..b4a2354a0 100644
--- a/lnbits/extensions/copilot/views.py
+++ b/lnbits/extensions/copilot/views.py
@@ -15,7 +15,9 @@ templates = Jinja2Templates(directory="templates")
@copilot_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_user_exists)):
+async def index(
+ request: Request, user: User = Depends(check_user_exists) # type: ignore
+):
return copilot_renderer().TemplateResponse(
"copilot/index.html", {"request": request, "user": user.dict()}
)
@@ -44,7 +46,7 @@ class ConnectionManager:
async def connect(self, websocket: WebSocket, copilot_id: str):
await websocket.accept()
- websocket.id = copilot_id
+ websocket.id = copilot_id # type: ignore
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
@@ -52,7 +54,7 @@ class ConnectionManager:
async def send_personal_message(self, message: str, copilot_id: str):
for connection in self.active_connections:
- if connection.id == copilot_id:
+ if connection.id == copilot_id: # type: ignore
await connection.send_text(message)
async def broadcast(self, message: str):
diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py
index 91b0572a5..46611a2ea 100644
--- a/lnbits/extensions/copilot/views_api.py
+++ b/lnbits/extensions/copilot/views_api.py
@@ -23,7 +23,7 @@ from .views import updater
@copilot_ext.get("/api/v1/copilot")
async def api_copilots_retrieve(
- req: Request, wallet: WalletTypeInfo = Depends(get_key_type)
+ req: Request, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
wallet_user = wallet.wallet.user
copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)]
@@ -37,7 +37,7 @@ async def api_copilots_retrieve(
async def api_copilot_retrieve(
req: Request,
copilot_id: str = Query(None),
- wallet: WalletTypeInfo = Depends(get_key_type),
+ wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
):
copilot = await get_copilot(copilot_id)
if not copilot:
@@ -54,7 +54,7 @@ async def api_copilot_retrieve(
async def api_copilot_create_or_update(
data: CreateCopilotData,
copilot_id: str = Query(None),
- wallet: WalletTypeInfo = Depends(require_admin_key),
+ wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
):
data.user = wallet.wallet.user
data.wallet = wallet.wallet.id
@@ -67,7 +67,8 @@ async def api_copilot_create_or_update(
@copilot_ext.delete("/api/v1/copilot/{copilot_id}")
async def api_copilot_delete(
- copilot_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)
+ copilot_id: str = Query(None),
+ wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
):
copilot = await get_copilot(copilot_id)
diff --git a/lnbits/extensions/discordbot/crud.py b/lnbits/extensions/discordbot/crud.py
index 5661fcb4d..629a5c004 100644
--- a/lnbits/extensions/discordbot/crud.py
+++ b/lnbits/extensions/discordbot/crud.py
@@ -98,21 +98,21 @@ async def get_discordbot_wallet(wallet_id: str) -> Optional[Wallets]:
return Wallets(**row) if row else None
-async def get_discordbot_wallets(admin_id: str) -> Optional[Wallets]:
+async def get_discordbot_wallets(admin_id: str) -> List[Wallets]:
rows = await db.fetchall(
"SELECT * FROM discordbot.wallets WHERE admin = ?", (admin_id,)
)
return [Wallets(**row) for row in rows]
-async def get_discordbot_users_wallets(user_id: str) -> Optional[Wallets]:
+async def get_discordbot_users_wallets(user_id: str) -> List[Wallets]:
rows = await db.fetchall(
"""SELECT * FROM discordbot.wallets WHERE "user" = ?""", (user_id,)
)
return [Wallets(**row) for row in rows]
-async def get_discordbot_wallet_transactions(wallet_id: str) -> Optional[Payment]:
+async def get_discordbot_wallet_transactions(wallet_id: str) -> List[Payment]:
return await get_payments(
wallet_id=wallet_id, complete=True, pending=False, outgoing=True, incoming=True
)
diff --git a/lnbits/extensions/discordbot/views.py b/lnbits/extensions/discordbot/views.py
index a5395e21f..ec7d18cc3 100644
--- a/lnbits/extensions/discordbot/views.py
+++ b/lnbits/extensions/discordbot/views.py
@@ -9,7 +9,9 @@ from . import discordbot_ext, discordbot_renderer
@discordbot_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_user_exists)):
+async def index(
+ request: Request, user: User = Depends(check_user_exists) # type: ignore
+):
return discordbot_renderer().TemplateResponse(
"discordbot/index.html", {"request": request, "user": user.dict()}
)
diff --git a/lnbits/extensions/discordbot/views_api.py b/lnbits/extensions/discordbot/views_api.py
index 6f213a89a..e6d004dbb 100644
--- a/lnbits/extensions/discordbot/views_api.py
+++ b/lnbits/extensions/discordbot/views_api.py
@@ -27,32 +27,37 @@ from .models import CreateUserData, CreateUserWallet
@discordbot_ext.get("/api/v1/users", status_code=HTTPStatus.OK)
-async def api_discordbot_users(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_discordbot_users(
+ wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
+):
user_id = wallet.wallet.user
return [user.dict() for user in await get_discordbot_users(user_id)]
@discordbot_ext.get("/api/v1/users/{user_id}", status_code=HTTPStatus.OK)
-async def api_discordbot_user(user_id, wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_discordbot_user(
+ user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
+):
user = await get_discordbot_user(user_id)
- return user.dict()
+ if user:
+ return user.dict()
@discordbot_ext.post("/api/v1/users", status_code=HTTPStatus.CREATED)
async def api_discordbot_users_create(
- data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type)
+ data: CreateUserData, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
user = await create_discordbot_user(data)
full = user.dict()
- full["wallets"] = [
- wallet.dict() for wallet in await get_discordbot_users_wallets(user.id)
- ]
+ wallets = await get_discordbot_users_wallets(user.id)
+ if wallets:
+ full["wallets"] = [wallet for wallet in wallets]
return full
@discordbot_ext.delete("/api/v1/users/{user_id}")
async def api_discordbot_users_delete(
- user_id, wallet: WalletTypeInfo = Depends(get_key_type)
+ user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
user = await get_discordbot_user(user_id)
if not user:
@@ -75,7 +80,7 @@ async def api_discordbot_activate_extension(
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
- update_user_extension(user_id=userid, extension=extension, active=active)
+ await update_user_extension(user_id=userid, extension=extension, active=active)
return {"extension": "updated"}
@@ -84,7 +89,7 @@ async def api_discordbot_activate_extension(
@discordbot_ext.post("/api/v1/wallets")
async def api_discordbot_wallets_create(
- data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type)
+ data: CreateUserWallet, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
user = await create_discordbot_wallet(
user_id=data.user_id, wallet_name=data.wallet_name, admin_id=data.admin_id
@@ -93,28 +98,30 @@ async def api_discordbot_wallets_create(
@discordbot_ext.get("/api/v1/wallets")
-async def api_discordbot_wallets(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_discordbot_wallets(
+ wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
+):
admin_id = wallet.wallet.user
- return [wallet.dict() for wallet in await get_discordbot_wallets(admin_id)]
+ return await get_discordbot_wallets(admin_id)
@discordbot_ext.get("/api/v1/transactions/{wallet_id}")
async def api_discordbot_wallet_transactions(
- wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
+ wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
return await get_discordbot_wallet_transactions(wallet_id)
@discordbot_ext.get("/api/v1/wallets/{user_id}")
async def api_discordbot_users_wallets(
- user_id, wallet: WalletTypeInfo = Depends(get_key_type)
+ user_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
- return [s_wallet.dict() for s_wallet in await get_discordbot_users_wallets(user_id)]
+ return await get_discordbot_users_wallets(user_id)
@discordbot_ext.delete("/api/v1/wallets/{wallet_id}")
async def api_discordbot_wallets_delete(
- wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
+ wallet_id, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
get_wallet = await get_discordbot_wallet(wallet_id)
if not get_wallet:
diff --git a/lnbits/extensions/example/views.py b/lnbits/extensions/example/views.py
index 252b47263..29b257f45 100644
--- a/lnbits/extensions/example/views.py
+++ b/lnbits/extensions/example/views.py
@@ -12,7 +12,10 @@ templates = Jinja2Templates(directory="templates")
@example_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_user_exists)):
+async def index(
+ request: Request,
+ user: User = Depends(check_user_exists), # type: ignore
+):
return example_renderer().TemplateResponse(
"example/index.html", {"request": request, "user": user.dict()}
)
diff --git a/lnbits/extensions/subdomains/crud.py b/lnbits/extensions/subdomains/crud.py
index 207e2d1d4..aa358d11c 100644
--- a/lnbits/extensions/subdomains/crud.py
+++ b/lnbits/extensions/subdomains/crud.py
@@ -3,10 +3,10 @@ from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
-from .models import CreateDomain, Domains, Subdomains
+from .models import CreateDomain, CreateSubdomain, Domains, Subdomains
-async def create_subdomain(payment_hash, wallet, data: CreateDomain) -> Subdomains:
+async def create_subdomain(payment_hash, wallet, data: CreateSubdomain) -> Subdomains:
await db.execute(
"""
INSERT INTO subdomains.subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid, record_type)
diff --git a/lnbits/extensions/subdomains/models.py b/lnbits/extensions/subdomains/models.py
index 170045040..39e176155 100644
--- a/lnbits/extensions/subdomains/models.py
+++ b/lnbits/extensions/subdomains/models.py
@@ -3,24 +3,24 @@ from pydantic.main import BaseModel
class CreateDomain(BaseModel):
- wallet: str = Query(...)
- domain: str = Query(...)
- cf_token: str = Query(...)
- cf_zone_id: str = Query(...)
- webhook: str = Query("")
- description: str = Query(..., min_length=0)
- cost: int = Query(..., ge=0)
- allowed_record_types: str = Query(...)
+ wallet: str = Query(...) # type: ignore
+ domain: str = Query(...) # type: ignore
+ cf_token: str = Query(...) # type: ignore
+ cf_zone_id: str = Query(...) # type: ignore
+ webhook: str = Query("") # type: ignore
+ description: str = Query(..., min_length=0) # type: ignore
+ cost: int = Query(..., ge=0) # type: ignore
+ allowed_record_types: str = Query(...) # type: ignore
class CreateSubdomain(BaseModel):
- domain: str = Query(...)
- subdomain: str = Query(...)
- email: str = Query(...)
- ip: str = Query(...)
- sats: int = Query(..., ge=0)
- duration: int = Query(...)
- record_type: str = Query(...)
+ domain: str = Query(...) # type: ignore
+ subdomain: str = Query(...) # type: ignore
+ email: str = Query(...) # type: ignore
+ ip: str = Query(...) # type: ignore
+ sats: int = Query(..., ge=0) # type: ignore
+ duration: int = Query(...) # type: ignore
+ record_type: str = Query(...) # type: ignore
class Domains(BaseModel):
diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py
index 04ee2dd48..c5a7f47ba 100644
--- a/lnbits/extensions/subdomains/tasks.py
+++ b/lnbits/extensions/subdomains/tasks.py
@@ -20,7 +20,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
- if payment.extra.get("tag") != "lnsubdomain":
+ if not payment.extra or payment.extra.get("tag") != "lnsubdomain":
# not an lnurlp invoice
return
@@ -37,7 +37,7 @@ async def on_invoice_paid(payment: Payment) -> None:
)
### Use webhook to notify about cloudflare registration
- if domain.webhook:
+ if domain and domain.webhook:
async with httpx.AsyncClient() as client:
try:
r = await client.post(
diff --git a/lnbits/extensions/subdomains/views.py b/lnbits/extensions/subdomains/views.py
index df387ba8c..962f850d0 100644
--- a/lnbits/extensions/subdomains/views.py
+++ b/lnbits/extensions/subdomains/views.py
@@ -16,7 +16,9 @@ templates = Jinja2Templates(directory="templates")
@subdomains_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_user_exists)):
+async def index(
+ request: Request, user: User = Depends(check_user_exists) # type:ignore
+):
return subdomains_renderer().TemplateResponse(
"subdomains/index.html", {"request": request, "user": user.dict()}
)
diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py
index b01e6ffbb..34d8e75be 100644
--- a/lnbits/extensions/subdomains/views_api.py
+++ b/lnbits/extensions/subdomains/views_api.py
@@ -29,12 +29,15 @@ from .crud import (
@subdomains_ext.get("/api/v1/domains")
async def api_domains(
- g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
+ g: WalletTypeInfo = Depends(get_key_type), # type: ignore
+ all_wallets: bool = Query(False),
):
wallet_ids = [g.wallet.id]
if all_wallets:
- wallet_ids = (await get_user(g.wallet.user)).wallet_ids
+ user = await get_user(g.wallet.user)
+ if user is not None:
+ wallet_ids = user.wallet_ids
return [domain.dict() for domain in await get_domains(wallet_ids)]
@@ -42,7 +45,9 @@ async def api_domains(
@subdomains_ext.post("/api/v1/domains")
@subdomains_ext.put("/api/v1/domains/{domain_id}")
async def api_domain_create(
- data: CreateDomain, domain_id=None, g: WalletTypeInfo = Depends(get_key_type)
+ data: CreateDomain,
+ domain_id=None,
+ g: WalletTypeInfo = Depends(get_key_type), # type: ignore
):
if domain_id:
domain = await get_domain(domain_id)
@@ -63,7 +68,9 @@ async def api_domain_create(
@subdomains_ext.delete("/api/v1/domains/{domain_id}")
-async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type)):
+async def api_domain_delete(
+ domain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore
+):
domain = await get_domain(domain_id)
if not domain:
@@ -82,12 +89,14 @@ async def api_domain_delete(domain_id, g: WalletTypeInfo = Depends(get_key_type)
@subdomains_ext.get("/api/v1/subdomains")
async def api_subdomains(
- all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type)
+ all_wallets: bool = Query(False), g: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
wallet_ids = [g.wallet.id]
if all_wallets:
- wallet_ids = (await get_user(g.wallet.user)).wallet_ids
+ user = await get_user(g.wallet.user)
+ if user is not None:
+ wallet_ids = user.wallet_ids
return [domain.dict() for domain in await get_subdomains(wallet_ids)]
@@ -173,7 +182,9 @@ async def api_subdomain_send_subdomain(payment_hash):
@subdomains_ext.delete("/api/v1/subdomains/{subdomain_id}")
-async def api_subdomain_delete(subdomain_id, g: WalletTypeInfo = Depends(get_key_type)):
+async def api_subdomain_delete(
+ subdomain_id, g: WalletTypeInfo = Depends(get_key_type) # type: ignore
+):
subdomain = await get_subdomain(subdomain_id)
if not subdomain:
diff --git a/pyproject.toml b/pyproject.toml
index 19dac8600..7418de272 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -89,8 +89,34 @@ profile = "black"
ignore_missing_imports = "True"
files = "lnbits"
exclude = """(?x)(
- ^lnbits/extensions.
- | ^lnbits/wallets/lnd_grpc_files.
+ ^lnbits/extensions/bleskomat.
+ | ^lnbits/extensions/boltz.
+ | ^lnbits/extensions/boltcards.
+ | ^lnbits/extensions/events.
+ | ^lnbits/extensions/hivemind.
+ | ^lnbits/extensions/invoices.
+ | ^lnbits/extensions/jukebox.
+ | ^lnbits/extensions/livestream.
+ | ^lnbits/extensions/lnaddress.
+ | ^lnbits/extensions/lndhub.
+ | ^lnbits/extensions/lnticket.
+ | ^lnbits/extensions/lnurldevice.
+ | ^lnbits/extensions/lnurlp.
+ | ^lnbits/extensions/lnurlpayout.
+ | ^lnbits/extensions/ngrok.
+ | ^lnbits/extensions/offlineshop.
+ | ^lnbits/extensions/paywall.
+ | ^lnbits/extensions/satsdice.
+ | ^lnbits/extensions/satspay.
+ | ^lnbits/extensions/scrub.
+ | ^lnbits/extensions/splitpayments.
+ | ^lnbits/extensions/streamalerts.
+ | ^lnbits/extensions/tipjar.
+ | ^lnbits/extensions/tpos.
+ | ^lnbits/extensions/usermanager.
+ | ^lnbits/extensions/watchonly.
+ | ^lnbits/extensions/withdraw.
+ | ^lnbits/wallets/lnd_grpc_files.
)"""
[tool.pytest.ini_options]
From 708f855c1d485639dfa559c2f2a26a5a9f41c1d7 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:03:32 +0000
Subject: [PATCH 043/134] Added old admin extension
---
.env.example | 14 +-
lnbits/extensions/admin/README.md | 11 +
lnbits/extensions/admin/__init__.py | 10 +
lnbits/extensions/admin/config.json | 6 +
lnbits/extensions/admin/crud.py | 59 ++
lnbits/extensions/admin/migrations.py | 256 ++++++++
lnbits/extensions/admin/models.py | 38 ++
.../admin/templates/admin/index.html | 565 ++++++++++++++++++
lnbits/extensions/admin/views.py | 20 +
lnbits/extensions/admin/views_api.py | 41 ++
10 files changed, 1014 insertions(+), 6 deletions(-)
create mode 100644 lnbits/extensions/admin/README.md
create mode 100644 lnbits/extensions/admin/__init__.py
create mode 100644 lnbits/extensions/admin/config.json
create mode 100644 lnbits/extensions/admin/crud.py
create mode 100644 lnbits/extensions/admin/migrations.py
create mode 100644 lnbits/extensions/admin/models.py
create mode 100644 lnbits/extensions/admin/templates/admin/index.html
create mode 100644 lnbits/extensions/admin/views.py
create mode 100644 lnbits/extensions/admin/views_api.py
diff --git a/.env.example b/.env.example
index 987c6ca69..bfaeb5152 100644
--- a/.env.example
+++ b/.env.example
@@ -3,17 +3,19 @@ PORT=5000
DEBUG=false
-LNBITS_ALLOWED_USERS=""
-LNBITS_ADMIN_USERS=""
-# Extensions only admin can access
-LNBITS_ADMIN_EXTENSIONS="ngrok"
+LNBITS_ADMIN_USERS="" # User IDs seperated by comma
+LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Extensions only admin can access
+
+LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
+
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# csv ad image filepaths or urls, extensions can choose to honor
LNBITS_AD_SPACE=""
# Hides wallet api, extensions can choose to honor
-LNBITS_HIDE_API=false
+LNBITS_HIDE_API=false
# Disable extensions for all users, use "all" to disable all extensions
LNBITS_DISABLED_EXTENSIONS="amilk"
@@ -67,7 +69,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY
LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
-# To use an AES-encrypted macaroon, set
+# To use an AES-encrypted macaroon, set
# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
# LNPayWallet
diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md
new file mode 100644
index 000000000..277294592
--- /dev/null
+++ b/lnbits/extensions/admin/README.md
@@ -0,0 +1,11 @@
+Example Extension
+*tagline*
+This is an example extension to help you organise and build you own.
+
+Try to include an image
+
+
+
+If your extension has API endpoints, include useful ones here
+
+curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
new file mode 100644
index 000000000..d5f26c90d
--- /dev/null
+++ b/lnbits/extensions/admin/__init__.py
@@ -0,0 +1,10 @@
+from quart import Blueprint
+from lnbits.db import Database
+
+db = Database("ext_admin")
+
+admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates")
+
+
+from .views_api import * # noqa
+from .views import * # noqa
diff --git a/lnbits/extensions/admin/config.json b/lnbits/extensions/admin/config.json
new file mode 100644
index 000000000..696617335
--- /dev/null
+++ b/lnbits/extensions/admin/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Admin",
+ "short_description": "Manage your LNbits install",
+ "icon": "build",
+ "contributors": ["benarc"]
+}
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
new file mode 100644
index 000000000..cb8f9b5be
--- /dev/null
+++ b/lnbits/extensions/admin/crud.py
@@ -0,0 +1,59 @@
+from typing import List, Optional
+
+from . import db
+from .models import Admin, Funding
+from lnbits.settings import *
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.core.crud import create_payment
+from lnbits.db import Connection
+
+
+def update_wallet_balance(wallet_id: str, amount: int) -> str:
+ temp_id = f"temp_{urlsafe_short_hash()}"
+ internal_id = f"internal_{urlsafe_short_hash()}"
+ create_payment(
+ wallet_id=wallet_id,
+ checking_id=internal_id,
+ payment_request="admin_internal",
+ payment_hash="admin_internal",
+ amount=amount * 1000,
+ memo="Admin top up",
+ pending=False,
+ )
+ return "success"
+
+
+async def update_admin(
+) -> Optional[Admin]:
+ if not CLightningWallet:
+ print("poo")
+ await db.execute(
+ """
+ UPDATE admin
+ SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ?
+ WHERE 1
+ """,
+ (
+
+ ),
+ )
+ row = await db.fetchone("SELECT * FROM admin WHERE 1")
+ return Admin.from_row(row) if row else None
+
+async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id)
+ )
+ row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
+ return Jukebox(**row) if row else None
+
+async def get_admin() -> List[Admin]:
+ row = await db.fetchone("SELECT * FROM admin WHERE 1")
+ return Admin.from_row(row) if row else None
+
+
+async def get_funding() -> List[Funding]:
+ rows = await db.fetchall("SELECT * FROM funding")
+
+ return [Funding.from_row(row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
new file mode 100644
index 000000000..82d934cb6
--- /dev/null
+++ b/lnbits/extensions/admin/migrations.py
@@ -0,0 +1,256 @@
+from sqlalchemy.exc import OperationalError # type: ignore
+from os import getenv
+from lnbits.helpers import urlsafe_short_hash
+
+
+async def m001_create_admin_table(db):
+ user = None
+ site_title = None
+ site_tagline = None
+ site_description = None
+ allowed_users = None
+ admin_user = None
+ default_wallet_name = None
+ data_folder = None
+ disabled_ext = None
+ force_https = True
+ service_fee = 0
+ funding_source = ""
+
+ if getenv("LNBITS_SITE_TITLE"):
+ site_title = getenv("LNBITS_SITE_TITLE")
+
+ if getenv("LNBITS_SITE_TAGLINE"):
+ site_tagline = getenv("LNBITS_SITE_TAGLINE")
+
+ if getenv("LNBITS_SITE_DESCRIPTION"):
+ site_description = getenv("LNBITS_SITE_DESCRIPTION")
+
+ if getenv("LNBITS_ALLOWED_USERS"):
+ allowed_users = getenv("LNBITS_ALLOWED_USERS")
+
+ if getenv("LNBITS_ADMIN_USER"):
+ admin_user = getenv("LNBITS_ADMIN_USER")
+
+ if getenv("LNBITS_DEFAULT_WALLET_NAME"):
+ default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
+
+ if getenv("LNBITS_DATA_FOLDER"):
+ data_folder = getenv("LNBITS_DATA_FOLDER")
+
+ if getenv("LNBITS_DISABLED_EXTENSIONS"):
+ disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+
+ if getenv("LNBITS_FORCE_HTTPS"):
+ force_https = getenv("LNBITS_FORCE_HTTPS")
+
+ if getenv("LNBITS_SERVICE_FEE"):
+ service_fee = getenv("LNBITS_SERVICE_FEE")
+
+ if getenv("LNBITS_BACKEND_WALLET_CLASS"):
+ funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
+
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS admin (
+ user TEXT,
+ site_title TEXT,
+ site_tagline TEXT,
+ site_description TEXT,
+ admin_user TEXT,
+ allowed_users TEXT,
+ default_wallet_name TEXT,
+ data_folder TEXT,
+ disabled_ext TEXT,
+ force_https BOOLEAN,
+ service_fee INT,
+ funding_source TEXT
+ );
+ """
+ )
+ await db.execute(
+ """
+ INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ user,
+ site_title,
+ site_tagline,
+ site_description,
+ admin_user,
+ allowed_users,
+ default_wallet_name,
+ data_folder,
+ disabled_ext,
+ force_https,
+ service_fee,
+ funding_source,
+ ),
+ )
+
+
+async def m001_create_funding_table(db):
+
+ funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
+
+ # Make the funding table, if it does not already exist
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS funding (
+ id TEXT PRIMARY KEY,
+ backend_wallet TEXT,
+ endpoint TEXT,
+ port INT,
+ read_key TEXT,
+ invoice_key TEXT,
+ admin_key TEXT,
+ cert TEXT,
+ balance INT,
+ selected INT
+ );
+ """
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, selected)
+ VALUES (?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "CLightningWallet",
+ getenv("CLIGHTNING_RPC"),
+ 1 if funding_wallet == "CLightningWallet" else 0,
+ ),
+ )
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "SparkWallet",
+ getenv("SPARK_URL"),
+ getenv("SPARK_TOKEN"),
+ 1 if funding_wallet == "SparkWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LnbitsWallet",
+ getenv("LNBITS_ENDPOINT"),
+ getenv("LNBITS_KEY"),
+ 1 if funding_wallet == "LnbitsWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LndWallet",
+ getenv("LND_GRPC_ENDPOINT"),
+ getenv("LND_GRPC_PORT"),
+ getenv("LND_GRPC_MACAROON"),
+ getenv("LND_GRPC_CERT"),
+ 1 if funding_wallet == "LndWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LndRestWallet",
+ getenv("LND_REST_ENDPOINT"),
+ getenv("LND_REST_MACAROON"),
+ getenv("LND_REST_CERT"),
+ 1 if funding_wallet == "LndWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LNPayWallet",
+ getenv("LNPAY_API_ENDPOINT"),
+ getenv("LNPAY_WALLET_KEY"),
+ getenv("LNPAY_API_KEY"), # this is going in as the cert
+ 1 if funding_wallet == "LNPayWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LntxbotWallet",
+ getenv("LNTXBOT_API_ENDPOINT"),
+ getenv("LNTXBOT_KEY"),
+ 1 if funding_wallet == "LntxbotWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "OpenNodeWallet",
+ getenv("OPENNODE_API_ENDPOINT"),
+ getenv("OPENNODE_KEY"),
+ 1 if funding_wallet == "OpenNodeWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "SparkWallet",
+ getenv("SPARK_URL"),
+ getenv("SPARK_TOKEN"),
+ 1 if funding_wallet == "SparkWallet" else 0,
+ ),
+ )
+
+ ## PLACEHOLDER FOR ECLAIR WALLET
+ # await db.execute(
+ # """
+ # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ # VALUES (?, ?, ?, ?, ?)
+ # """,
+ # (
+ # urlsafe_short_hash(),
+ # "EclairWallet",
+ # getenv("ECLAIR_URL"),
+ # getenv("ECLAIR_PASS"),
+ # 1 if funding_wallet == "EclairWallet" else 0,
+ # ),
+ # )
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
new file mode 100644
index 000000000..c38f17f48
--- /dev/null
+++ b/lnbits/extensions/admin/models.py
@@ -0,0 +1,38 @@
+from typing import NamedTuple
+from sqlite3 import Row
+
+class Admin(NamedTuple):
+ user: str
+ site_title: str
+ site_tagline: str
+ site_description:str
+ allowed_users: str
+ admin_user: str
+ default_wallet_name: str
+ data_folder: str
+ disabled_ext: str
+ force_https: str
+ service_fee: str
+ funding_source: str
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Admin":
+ data = dict(row)
+ return cls(**data)
+
+class Funding(NamedTuple):
+ id: str
+ backend_wallet: str
+ endpoint: str
+ port: str
+ read_key: str
+ invoice_key: str
+ admin_key: str
+ cert: str
+ balance: int
+ selected: int
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Funding":
+ data = dict(row)
+ return cls(**data)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
new file mode 100644
index 000000000..87cf09efa
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -0,0 +1,565 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+Admin
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wallet topup
+
+
+
+
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+{% endblock %}
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
new file mode 100644
index 000000000..5e17919c5
--- /dev/null
+++ b/lnbits/extensions/admin/views.py
@@ -0,0 +1,20 @@
+from quart import g, render_template, request, jsonify
+import json
+
+from lnbits.decorators import check_user_exists, validate_uuids
+from lnbits.extensions.admin import admin_ext
+from lnbits.core.crud import get_user, create_account
+from .crud import get_admin, get_funding
+from lnbits.settings import WALLET
+
+
+@admin_ext.route("/")
+@validate_uuids(["usr"], required=True)
+@check_user_exists()
+async def index():
+ user_id = g.user
+ admin = await get_admin()
+
+ funding = [{**funding._asdict()} for funding in await get_funding()]
+
+ return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
new file mode 100644
index 000000000..2a61b6f55
--- /dev/null
+++ b/lnbits/extensions/admin/views_api.py
@@ -0,0 +1,41 @@
+from quart import jsonify, g, request
+from http import HTTPStatus
+from .crud import update_wallet_balance
+from lnbits.extensions.admin import admin_ext
+from lnbits.decorators import api_check_wallet_key, api_validate_post_request
+from lnbits.core.crud import get_wallet
+from .crud import get_admin,update_admin
+import json
+
+@admin_ext.route("/api/v1/admin//", methods=["GET"])
+@api_check_wallet_key("admin")
+async def api_update_balance(wallet_id, topup_amount):
+ print(g.data.wallet)
+ try:
+ wallet = await get_wallet(wallet_id)
+ except:
+ return (
+ jsonify({"error": "Not allowed: not an admin"}),
+ HTTPStatus.FORBIDDEN,
+ )
+ print(wallet)
+ print(topup_amount)
+ return jsonify({"status": "Success"}), HTTPStatus.OK
+
+
+@admin_ext.route("/api/v1/admin/", methods=["POST"])
+@api_check_wallet_key("admin")
+@api_validate_post_request(schema={})
+async def api_update_admin():
+ body = await request.get_json()
+ admin = await get_admin()
+ print(g.wallet[2])
+ print(body["admin_user"])
+ if not admin.admin_user == g.wallet[2] and admin.admin_user != None:
+ return (
+ jsonify({"error": "Not allowed: not an admin"}),
+ HTTPStatus.FORBIDDEN,
+ )
+ updated = await update_admin(body)
+ print(updated)
+ return jsonify({"status": "Success"}), HTTPStatus.OK
\ No newline at end of file
From a3b1d9528c92008b42116904f5ebbdf8d9360173 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:11:55 +0000
Subject: [PATCH 044/134] old admin setup UI
---
lnbits/core/templates/core/admin.html | 717 ++++++++++++++++++++++++++
1 file changed, 717 insertions(+)
create mode 100644 lnbits/core/templates/core/admin.html
diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html
new file mode 100644
index 000000000..e81765550
--- /dev/null
+++ b/lnbits/core/templates/core/admin.html
@@ -0,0 +1,717 @@
+{% extends "public.html" %} {% from "macros.jinja" import window_vars with
+context %} {% block page %}
+
+
+
+
+
+ Welcome to LNbits
+
+
+ Fill in the information below to setup your LNbits instance. Details
+ can be changed later.
+
+
+
+
+
+
+ Branding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Service settings
+
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details
+ should be filled in for you
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View project in GitHub
+ Donate
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }}
+
+{% endblock %}
From b325566302f079575e478d9ca9eeff47a8f10a1a Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:18:09 +0000
Subject: [PATCH 045/134] convert to FastAPI
---
lnbits/extensions/admin/__init__.py | 11 +++-
lnbits/extensions/admin/crud.py | 50 +++++++--------
lnbits/extensions/admin/migrations.py | 23 ++++---
lnbits/extensions/admin/models.py | 51 ++++++++++------
.../admin/templates/admin/index.html | 23 +++++--
lnbits/extensions/admin/views.py | 39 ++++++++----
lnbits/extensions/admin/views_api.py | 61 ++++++++++---------
7 files changed, 151 insertions(+), 107 deletions(-)
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
index d5f26c90d..6a56b2bb1 100644
--- a/lnbits/extensions/admin/__init__.py
+++ b/lnbits/extensions/admin/__init__.py
@@ -1,10 +1,15 @@
-from quart import Blueprint
+from fastapi import APIRouter
+
from lnbits.db import Database
+from lnbits.helpers import template_renderer
db = Database("ext_admin")
-admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates")
+admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"])
+
+def admin_renderer():
+ return template_renderer(["lnbits/extensions/admin/templates"])
-from .views_api import * # noqa
from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index cb8f9b5be..872d6c97b 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,11 +1,11 @@
from typing import List, Optional
+from lnbits.core.crud import create_payment
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.settings import *
+
from . import db
from .models import Admin, Funding
-from lnbits.settings import *
-from lnbits.helpers import urlsafe_short_hash
-from lnbits.core.crud import create_payment
-from lnbits.db import Connection
def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -22,38 +22,30 @@ def update_wallet_balance(wallet_id: str, amount: int) -> str:
)
return "success"
-
-async def update_admin(
-) -> Optional[Admin]:
- if not CLightningWallet:
- print("poo")
- await db.execute(
- """
- UPDATE admin
- SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ?
- WHERE 1
- """,
- (
-
- ),
- )
- row = await db.fetchone("SELECT * FROM admin WHERE 1")
- return Admin.from_row(row) if row else None
-
-async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]:
+async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ print("UPDATE", q)
await db.execute(
- f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id)
+ f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
- return Jukebox(**row) if row else None
+ row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,))
+ assert row, "Newly updated settings couldn't be retrieved"
+ return Admin(**row) if row else None
+
+# async def update_admin(user: str, **kwargs) -> Optional[Admin]:
+# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+# await db.execute(
+# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user)
+# )
+# new_settings = await get_admin()
+# return new_settings
async def get_admin() -> List[Admin]:
- row = await db.fetchone("SELECT * FROM admin WHERE 1")
- return Admin.from_row(row) if row else None
+ row = await db.fetchone("SELECT * FROM admin")
+ return Admin(**row) if row else None
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM funding")
- return [Funding.from_row(row) for row in rows]
+ return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 82d934cb6..13b769232 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -1,5 +1,7 @@
-from sqlalchemy.exc import OperationalError # type: ignore
from os import getenv
+
+from sqlalchemy.exc import OperationalError # type: ignore
+
from lnbits.helpers import urlsafe_short_hash
@@ -9,7 +11,7 @@ async def m001_create_admin_table(db):
site_tagline = None
site_description = None
allowed_users = None
- admin_user = None
+ admin_users = None
default_wallet_name = None
data_folder = None
disabled_ext = None
@@ -29,8 +31,9 @@ async def m001_create_admin_table(db):
if getenv("LNBITS_ALLOWED_USERS"):
allowed_users = getenv("LNBITS_ALLOWED_USERS")
- if getenv("LNBITS_ADMIN_USER"):
- admin_user = getenv("LNBITS_ADMIN_USER")
+ if getenv("LNBITS_ADMIN_USERS"):
+ admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
+ user = admin_users.split(',')[0]
if getenv("LNBITS_DEFAULT_WALLET_NAME"):
default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
@@ -53,32 +56,32 @@ async def m001_create_admin_table(db):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin (
- user TEXT,
+ "user" TEXT,
site_title TEXT,
site_tagline TEXT,
site_description TEXT,
- admin_user TEXT,
+ admin_users TEXT,
allowed_users TEXT,
default_wallet_name TEXT,
data_folder TEXT,
disabled_ext TEXT,
force_https BOOLEAN,
- service_fee INT,
+ service_fee REAL,
funding_source TEXT
);
"""
)
await db.execute(
"""
- INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
+ INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
- user,
+ user.strip(),
site_title,
site_tagline,
site_description,
- admin_user,
+ admin_users[1:],
allowed_users,
default_wallet_name,
data_folder,
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index c38f17f48..4080ff018 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,18 +1,35 @@
-from typing import NamedTuple
from sqlite3 import Row
+from typing import List, Optional
-class Admin(NamedTuple):
+from fastapi import Query
+from pydantic import BaseModel
+
+
+class UpdateAdminSettings(BaseModel):
+ site_title: Optional[str]
+ site_tagline: Optional[str]
+ site_description: Optional[str]
+ allowed_users: Optional[str]
+ admin_users: Optional[str]
+ default_wallet_name: Optional[str]
+ data_folder: Optional[str]
+ disabled_ext: Optional[str]
+ force_https: Optional[bool]
+ service_fee: Optional[float]
+ funding_source: Optional[str]
+
+class Admin(BaseModel):
user: str
- site_title: str
- site_tagline: str
- site_description:str
- allowed_users: str
- admin_user: str
+ site_title: Optional[str]
+ site_tagline: Optional[str]
+ site_description: Optional[str]
+ allowed_users: Optional[str]
+ admin_users: str
default_wallet_name: str
data_folder: str
disabled_ext: str
- force_https: str
- service_fee: str
+ force_https: Optional[bool] = Query(True)
+ service_fee: float
funding_source: str
@classmethod
@@ -20,16 +37,16 @@ class Admin(NamedTuple):
data = dict(row)
return cls(**data)
-class Funding(NamedTuple):
+class Funding(BaseModel):
id: str
backend_wallet: str
- endpoint: str
- port: str
- read_key: str
- invoice_key: str
- admin_key: str
- cert: str
- balance: int
+ endpoint: str = Query(None)
+ port: str = Query(None)
+ read_key: str = Query(None)
+ invoice_key: str = Query(None)
+ admin_key: str = Query(None)
+ cert: str = Query(None)
+ balance: int = Query(None)
selected: int
@classmethod
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 87cf09efa..a6b456259 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -87,7 +87,7 @@
@@ -442,13 +442,14 @@
site_title: '{{admin.site_title}}',
tagline: '{{admin.site_tagline}}',
description: '{{admin.site_description}}',
- admin_user: '{{admin.admin_user}}',
- service_fee: parseInt('{{admin.service_fee}}'),
+ admin_users: '{{admin.admin_users}}',
+ service_fee: parseFloat('{{admin.service_fee}}'),
default_wallet_name: '{{admin.default_wallet_name}}',
data_folder: '{{admin.data_folder}}',
funding_source_primary: '{{admin.funding_source}}',
disabled_ext: '{{admin.disabled_ext}}'.split(','),
edited: [],
+ funding: {},
senddata: {}
}
},
@@ -528,15 +529,27 @@
},
UpdateLNbits: function () {
var self = this
- console.log(self.data.admin)
+ let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin
+ let data = {
+ site_title,
+ site_tagline: this.data.admin.tagline,
+ site_description: this.data.admin.description,
+ admin_users: admin_users.toString(),
+ default_wallet_name,
+ data_folder,
+ disabled_ext: disabled_ext.toString(),
+ service_fee,
+ funding_source: funding_source_primary}
+ console.log(data)
LNbits.api
.request(
'POST',
'/admin/api/v1/admin/',
self.g.user.wallets[0].adminkey,
- self.data.admin
+ data
)
.then(function (response) {
+ console.log(response.data)
self.$q.notify({
type: 'positive',
message:
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 5e17919c5..00a0c99fc 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -1,20 +1,33 @@
-from quart import g, render_template, request, jsonify
-import json
+from email.policy import default
+from os import getenv
-from lnbits.decorators import check_user_exists, validate_uuids
+from fastapi import Request
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.responses import HTMLResponse
+
+from lnbits.core.models import User
+from lnbits.decorators import check_user_exists
from lnbits.extensions.admin import admin_ext
-from lnbits.core.crud import get_user, create_account
+from lnbits.requestvars import g
+
+from . import admin_ext, admin_renderer
from .crud import get_admin, get_funding
-from lnbits.settings import WALLET
+templates = Jinja2Templates(directory="templates")
-@admin_ext.route("/")
-@validate_uuids(["usr"], required=True)
-@check_user_exists()
-async def index():
- user_id = g.user
+@admin_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
+ print(g())
+ funding = [f.dict() for f in await get_funding()]
- funding = [{**funding._asdict()} for funding in await get_funding()]
-
- return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding)
+ print("ADMIN", admin.dict())
+ return admin_renderer().TemplateResponse(
+ "admin/index.html", {
+ "request": request,
+ "user": user.dict(),
+ "admin": admin.dict(),
+ "funding": funding
+ }
+ )
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 2a61b6f55..b2c65be25 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,41 +1,42 @@
-from quart import jsonify, g, request
from http import HTTPStatus
-from .crud import update_wallet_balance
-from lnbits.extensions.admin import admin_ext
-from lnbits.decorators import api_check_wallet_key, api_validate_post_request
-from lnbits.core.crud import get_wallet
-from .crud import get_admin,update_admin
-import json
-@admin_ext.route("/api/v1/admin//", methods=["GET"])
-@api_check_wallet_key("admin")
-async def api_update_balance(wallet_id, topup_amount):
- print(g.data.wallet)
+from fastapi import Body, Depends, Request
+from starlette.exceptions import HTTPException
+
+from lnbits.core.crud import get_wallet
+from lnbits.decorators import WalletTypeInfo, require_admin_key
+from lnbits.extensions.admin import admin_ext
+from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+
+from .crud import get_admin, update_admin, update_wallet_balance
+
+
+@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
+async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)):
+ print(g.wallet)
try:
wallet = await get_wallet(wallet_id)
except:
- return (
- jsonify({"error": "Not allowed: not an admin"}),
- HTTPStatus.FORBIDDEN,
- )
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
print(wallet)
print(topup_amount)
- return jsonify({"status": "Success"}), HTTPStatus.OK
+ return {"status": "Success"}
-@admin_ext.route("/api/v1/admin/", methods=["POST"])
-@api_check_wallet_key("admin")
-@api_validate_post_request(schema={})
-async def api_update_admin():
- body = await request.get_json()
+@admin_ext.post("/api/v1/admin/", status_code=HTTPStatus.OK)
+async def api_update_admin(
+ request: Request,
+ data: UpdateAdminSettings = Body(...),
+ g: WalletTypeInfo = Depends(require_admin_key)
+ ):
admin = await get_admin()
- print(g.wallet[2])
- print(body["admin_user"])
- if not admin.admin_user == g.wallet[2] and admin.admin_user != None:
- return (
- jsonify({"error": "Not allowed: not an admin"}),
- HTTPStatus.FORBIDDEN,
- )
- updated = await update_admin(body)
+ print(data)
+ if not admin.user == g.wallet.user:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
+ updated = await update_admin(user=g.wallet.user, **data.dict())
print(updated)
- return jsonify({"status": "Success"}), HTTPStatus.OK
\ No newline at end of file
+ return {"status": "Success"}
From b4885de9e2fcf598994dd5ca2360cc29629aaa23 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:18:58 +0000
Subject: [PATCH 046/134] remove core admin html (renamed for now)
---
lnbits/core/templates/core/core_admin.html | 717 +++++++++++++++++++++
1 file changed, 717 insertions(+)
create mode 100644 lnbits/core/templates/core/core_admin.html
diff --git a/lnbits/core/templates/core/core_admin.html b/lnbits/core/templates/core/core_admin.html
new file mode 100644
index 000000000..835fc00a3
--- /dev/null
+++ b/lnbits/core/templates/core/core_admin.html
@@ -0,0 +1,717 @@
+{% extends "public.html" %} {% from "macros.jinja" import window_vars with
+context %} {% block page %}
+
+
+
+
+
+ Welcome to LNbits
+
+
+ Fill in the information below to setup your LNbits instance. Details
+ can be changed later.
+
+
+
+
+
+
+ Branding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Service settings
+
+
+
+ Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details
+ should be filled in for you
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View project in GitHub
+ Donate
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }}
+
+{% endblock %}
From c41b4e714c2e20c36d6a262c77966598b1fd85c4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:21:38 +0000
Subject: [PATCH 047/134] typo
---
.env.example | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.env.example b/.env.example
index bfaeb5152..4192f82ec 100644
--- a/.env.example
+++ b/.env.example
@@ -5,7 +5,7 @@ DEBUG=false
LNBITS_ADMIN_USERS="" # User IDs seperated by comma
LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS
LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
From 922206b365cff523a12bf5a8014b544aa88398f3 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:22:23 +0000
Subject: [PATCH 048/134] add admin_ui env
---
lnbits/settings.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 3f4e31cc5..43cb87cbe 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,5 +1,6 @@
import importlib
import subprocess
+from email.policy import default
from os import path
from typing import List
@@ -27,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
LNBITS_ALLOWED_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
]
+LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False)
LNBITS_ADMIN_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
]
From b47ed3068105f2526365f6708efe5134a5fe1c0e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:23:16 +0000
Subject: [PATCH 049/134] add db config at startup
---
lnbits/commands.py | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 0f7454f23..8c39c338f 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -52,6 +52,25 @@ def bundle_vendored():
with open(outputpath, "w") as f:
f.write(output)
+async def get_admin_settings():
+ from lnbits.extensions.admin.models import Admin
+
+ async with core_db.connect() as conn:
+
+ if conn.type == SQLITE:
+ exists = await conn.fetchone(
+ "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
+ )
+ elif conn.type in {POSTGRES, COCKROACH}:
+ exists = await conn.fetchone(
+ "SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
+ )
+ if not exists:
+ return False
+
+ row = await conn.fetchone("SELECT * from admin")
+
+ return Admin(**row) if row else None
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
From 4bf17c5df2d07443c972be9356be710e4578c79b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:23:53 +0000
Subject: [PATCH 050/134] get admin settings at startup
---
lnbits/app.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 075828ef0..7ff9e4eb5 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -18,6 +18,7 @@ from loguru import logger
import lnbits.settings
from lnbits.core.tasks import register_task_listeners
+from .commands import get_admin_settings
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@@ -42,6 +43,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
+<<<<<<< HEAD
configure_logger()
app = FastAPI(
@@ -53,6 +55,14 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
+=======
+ app = FastAPI()
+
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ check_settings(app)
+
+ app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
+>>>>>>> e3a1b3ae (get admin settings at startup)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
@@ -64,7 +74,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
app.add_middleware(
CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"]
)
-
g().config = lnbits.settings
g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}"
@@ -101,6 +110,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
+def check_settings(app: FastAPI):
+ @app.on_event("startup")
+ async def check_settings_admin():
+ while True:
+ admin_set = await get_admin_settings()
+ if admin_set :
+ break
+ print("ERROR:", admin_set)
+ await asyncio.sleep(5)
+ # admin_set = await get_admin_settings()
+ g().admin_conf = admin_set
+
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
async def check_wallet_status():
From 48d6a89e5ba9e1e082a2ff189b3307d685f521e4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:24:11 +0000
Subject: [PATCH 051/134] remove core admin.html
---
lnbits/core/templates/core/admin.html | 717 --------------------------
1 file changed, 717 deletions(-)
delete mode 100644 lnbits/core/templates/core/admin.html
diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html
deleted file mode 100644
index e81765550..000000000
--- a/lnbits/core/templates/core/admin.html
+++ /dev/null
@@ -1,717 +0,0 @@
-{% extends "public.html" %} {% from "macros.jinja" import window_vars with
-context %} {% block page %}
-
-
-
-
-
- Welcome to LNbits
-
-
- Fill in the information below to setup your LNbits instance. Details
- can be changed later.
-
-
-
-
-
-
- Branding
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Service settings
-
-
-
- Funding source information (at least one required)
*if installed through RaspiBlitz, MyNode, etc, details
- should be filled in for you
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- View project in GitHub
- Donate
-
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(funding) }}
-
-{% endblock %}
From 87bee88de403723b9d98f64716c573e21289a50a Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 18 Mar 2022 16:55:31 +0000
Subject: [PATCH 052/134] refactor ui
---
.../admin/templates/admin/index.html | 727 +++++++++++++++++-
1 file changed, 712 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index a6b456259..65ac9f336 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1,6 +1,670 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
+
+
+
+
+
+
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+
+
+
+
+
+
+
+ Wallets Management
+
+
+
+
+
Funding Source Info
+
+ {%raw%}
+ - Funding Source: {{data.admin.funding_source}}
+ - Balance: {{data.admin.balance / 1000}} sats
+ {%endraw%}
+
+
+
+
+
+
+
+
+ TopUp a wallet
+
+
+
+
+
+
+
+
+
Funding Sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ User Management
+
+
+ Super Admin: {% raw
+ %}{{this.data.admin.user}}{% endraw %}
+
+
+
+
Admin Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
Allowed Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
+
+
+ Server Management
+
+
+
+
+
Server Info
+
+ {%raw%}
+ - SQlite: {{data.admin.data_folder}}
+ - Postgres: {{data.admin.database_url}}
+ {%endraw%}
+
+
+
+
+
+
+
+
Miscelaneous
+
+
+ Force HTTPS
+ Prefer secure URLs
+
+
+
+
+
+
+
+ Hide API
+ Hides wallet api, extensions can choose to honor
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ UI Management
+
+
+
+
+
+
+
+
Default Wallet Name
+
+
+
+
+
+
+
+
+
Advertisement Slots
+
+
+
+
+ {% raw %}
+
+ {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
+
+ {% endraw %}
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+
+
+
+
Admin
-
+
@@ -426,6 +1090,7 @@
return {
wallet: {data: {}},
cancel: {},
+ tab: 'funding',
data: {
funding_source: [
'CLightningWallet',
@@ -436,24 +1101,14 @@
'LnbitsWallet',
'OpenNodeWallet'
],
-
+
admin: {
- user: '{{ user.id }}',
- site_title: '{{admin.site_title}}',
- tagline: '{{admin.site_tagline}}',
- description: '{{admin.site_description}}',
- admin_users: '{{admin.admin_users}}',
- service_fee: parseFloat('{{admin.service_fee}}'),
- default_wallet_name: '{{admin.default_wallet_name}}',
- data_folder: '{{admin.data_folder}}',
- funding_source_primary: '{{admin.funding_source}}',
- disabled_ext: '{{admin.disabled_ext}}'.split(','),
edited: [],
funding: {},
senddata: {}
}
},
-
+ themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'],
options: [
'bleskomat',
'captcha',
@@ -489,9 +1144,51 @@
for (i = 0; i < funding.length; i++) {
self.data.admin.funding[funding[i].backend_wallet] = funding[i]
}
- console.log(self.data.admin)
+ let settings = JSON.parse('{{ settings | tojson|safe }}')
+ settings.balance = '{{ balance }}'
+ this.data.admin = {...this.data.admin, ...settings}
+ console.log(this.g.user)
},
methods: {
+ addAdminUser(){
+ let addUser = this.data.admin_users_add
+ let admin_users = this.data.admin.admin_users
+ if(addUser.length && !admin_users.includes(addUser)){
+ admin_users.push(addUser)
+ this.data.admin.admin_users = admin_users
+ this.data.admin_users_add = ""
+ }
+ },
+ removeAdminUser(user){
+ let admin_users = this.data.admin.admin_users
+ this.data.admin.admin_users = admin_users.filter(u => u !== user)
+ },
+ addAllowedUser(){
+ let addUser = this.data.allowed_users_add
+ let allowed_users = this.data.admin.allowed_users
+ if(addUser.length && !allowed_users.includes(addUser)){
+ allowed_users.push(addUser)
+ this.data.admin.allowed_users = allowed_users
+ this.data.allowed_users_add = ""
+ }
+ },
+ removeAllowedUser(user){
+ let allowed_users = this.data.admin.allowed_users
+ this.data.admin.allowed_users = allowed_users.filter(u => u !== user)
+ },
+ addAdSpace(){
+ let adSpace = this.data.ad_space_add
+ let spaces = this.data.admin.ad_space
+ if(adSpace.length && !spaces.includes(adSpace)){
+ spaces.push(adSpace)
+ this.data.admin.ad_space = spaces
+ this.data.ad_space_add = ""
+ }
+ },
+ removeAdSpace(ad){
+ let spaces = this.data.admin.ad_space
+ this.data.admin.ad_space = spaces.filter(s => s !== ad)
+ },
topupWallet: function () {
var self = this
LNbits.api
From 8c1c7d13b87ba7b7408dccd719a9a9c66e6e4325 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 18 Mar 2022 16:59:06 +0000
Subject: [PATCH 053/134] make it work from g()
---
lnbits/app.py | 34 +++---
lnbits/config.py | 62 ++++++++++
lnbits/extensions/admin/crud.py | 2 +-
lnbits/extensions/admin/migrations.py | 162 +++++++++++++++++---------
lnbits/extensions/admin/models.py | 27 +++--
lnbits/extensions/admin/views.py | 8 +-
lnbits/settings.py | 2 +-
7 files changed, 218 insertions(+), 79 deletions(-)
create mode 100644 lnbits/config.py
diff --git a/lnbits/app.py b/lnbits/app.py
index 7ff9e4eb5..1ffedb54c 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -19,6 +19,7 @@ import lnbits.settings
from lnbits.core.tasks import register_task_listeners
from .commands import get_admin_settings
+from .config import WALLET, conf
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@@ -29,7 +30,8 @@ from .helpers import (
url_for_vendored,
)
from .requestvars import g
-from .settings import WALLET
+
+# from .settings import WALLET
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
@@ -43,7 +45,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
-<<<<<<< HEAD
configure_logger()
app = FastAPI(
@@ -55,20 +56,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
-=======
- app = FastAPI()
-
- if lnbits.settings.LNBITS_ADMIN_UI:
- check_settings(app)
-
- app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
->>>>>>> e3a1b3ae (get admin settings at startup)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
name="core_static",
)
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ g().admin_conf = conf
+ check_settings(app)
+
+ g().WALLET = WALLET
+
origins = ["*"]
app.add_middleware(
@@ -109,18 +108,27 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
-
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
+
+ def removeEmptyString(arr):
+ return list(filter(None, arr))
+
while True:
admin_set = await get_admin_settings()
if admin_set :
break
print("ERROR:", admin_set)
await asyncio.sleep(5)
- # admin_set = await get_admin_settings()
- g().admin_conf = admin_set
+
+ admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
+ admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(','))
+ admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(','))
+ admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(','))
+ admin_set.theme = removeEmptyString(admin_set.theme.split(','))
+ admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
+ g().admin_conf = conf.copy(update=admin_set.dict())
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
diff --git a/lnbits/config.py b/lnbits/config.py
new file mode 100644
index 000000000..02e8cf537
--- /dev/null
+++ b/lnbits/config.py
@@ -0,0 +1,62 @@
+import importlib
+import json
+from os import getenv, path
+from typing import List, Optional
+
+from pydantic import BaseSettings, Field, validator
+
+wallets_module = importlib.import_module("lnbits.wallets")
+wallet_class = getattr(
+ wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")
+)
+
+WALLET = wallet_class()
+
+def list_parse_fallback(v):
+ try:
+ return json.loads(v)
+ except Exception as e:
+ return v.replace(' ','').split(',')
+
+class Settings(BaseSettings):
+ # users
+ admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
+ allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
+ admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS")
+ disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS")
+ funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS")
+ # ops
+ data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER")
+ database_url: str = Field(default=None, env="LNBITS_DATABASE_URL")
+ force_https: bool = Field(default=True, env="LNBITS_FORCE_HTTPS")
+ service_fee: float = Field(default=0, env="LNBITS_SERVICE_FEE")
+ hide_api: bool = Field(default=False, env="LNBITS_HIDE_API")
+ denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
+ # Change theme
+ site_title: str = Field(default=None, env="LNBITS_SITE_TITLE")
+ site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE")
+ site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
+ default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME")
+ theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
+ ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
+ # .env
+ env: Optional[str]
+ debug: Optional[str]
+ host: Optional[str]
+ port: Optional[str]
+ lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
+
+ # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True)
+ # def validate(cls, val):
+ # print(val)
+ # return val.split(',')
+
+ class Config:
+ env_file = ".env"
+ env_file_encoding = "utf-8"
+ case_sensitive = False
+ json_loads = list_parse_fallback
+
+
+conf = Settings()
+WALLET = wallet_class()
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 872d6c97b..6fccb8ee6 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -40,7 +40,7 @@ async def update_admin(user: str, **kwargs) -> Admin:
# new_settings = await get_admin()
# return new_settings
-async def get_admin() -> List[Admin]:
+async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 13b769232..574f772d1 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -2,93 +2,151 @@ from os import getenv
from sqlalchemy.exc import OperationalError # type: ignore
+from lnbits.config import conf
from lnbits.helpers import urlsafe_short_hash
async def m001_create_admin_table(db):
- user = None
- site_title = None
- site_tagline = None
- site_description = None
- allowed_users = None
- admin_users = None
- default_wallet_name = None
- data_folder = None
- disabled_ext = None
- force_https = True
- service_fee = 0
- funding_source = ""
+ # users/server
+ user = conf.admin_users[0]
+ admin_users = ",".join(conf.admin_users)
+ allowed_users = ",".join(conf.allowed_users)
+ admin_ext = ",".join(conf.admin_ext)
+ disabled_ext = ",".join(conf.disabled_ext)
+ funding_source = conf.funding_source
+ #operational
+ data_folder = conf.data_folder
+ database_url = conf.database_url
+ force_https = conf.force_https
+ service_fee = conf.service_fee
+ hide_api = conf.hide_api
+ denomination = conf.denomination
+ # Theme'ing
+ site_title = conf.site_title
+ site_tagline = conf.site_tagline
+ site_description = conf.site_description
+ default_wallet_name = conf.default_wallet_name
+ theme = ",".join(conf.theme)
+ ad_space = ",".join(conf.ad_space)
- if getenv("LNBITS_SITE_TITLE"):
- site_title = getenv("LNBITS_SITE_TITLE")
+ # if getenv("LNBITS_ADMIN_EXTENSIONS"):
+ # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS")
- if getenv("LNBITS_SITE_TAGLINE"):
- site_tagline = getenv("LNBITS_SITE_TAGLINE")
+ # if getenv("LNBITS_DATABASE_URL"):
+ # database_url = getenv("LNBITS_DATABASE_URL")
- if getenv("LNBITS_SITE_DESCRIPTION"):
- site_description = getenv("LNBITS_SITE_DESCRIPTION")
+ # if getenv("LNBITS_HIDE_API"):
+ # hide_api = getenv("LNBITS_HIDE_API")
- if getenv("LNBITS_ALLOWED_USERS"):
- allowed_users = getenv("LNBITS_ALLOWED_USERS")
+ # if getenv("LNBITS_THEME_OPTIONS"):
+ # theme = getenv("LNBITS_THEME_OPTIONS")
- if getenv("LNBITS_ADMIN_USERS"):
- admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
- user = admin_users.split(',')[0]
+ # if getenv("LNBITS_AD_SPACE"):
+ # ad_space = getenv("LNBITS_AD_SPACE")
- if getenv("LNBITS_DEFAULT_WALLET_NAME"):
- default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
+ # if getenv("LNBITS_SITE_TITLE"):
+ # site_title = getenv("LNBITS_SITE_TITLE")
- if getenv("LNBITS_DATA_FOLDER"):
- data_folder = getenv("LNBITS_DATA_FOLDER")
+ # if getenv("LNBITS_SITE_TAGLINE"):
+ # site_tagline = getenv("LNBITS_SITE_TAGLINE")
- if getenv("LNBITS_DISABLED_EXTENSIONS"):
- disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+ # if getenv("LNBITS_SITE_DESCRIPTION"):
+ # site_description = getenv("LNBITS_SITE_DESCRIPTION")
- if getenv("LNBITS_FORCE_HTTPS"):
- force_https = getenv("LNBITS_FORCE_HTTPS")
+ # if getenv("LNBITS_ALLOWED_USERS"):
+ # allowed_users = getenv("LNBITS_ALLOWED_USERS")
- if getenv("LNBITS_SERVICE_FEE"):
- service_fee = getenv("LNBITS_SERVICE_FEE")
+ # if getenv("LNBITS_ADMIN_USERS"):
+ # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
+ # user = admin_users.split(',')[0]
+
+ # if getenv("LNBITS_DEFAULT_WALLET_NAME"):
+ # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
- if getenv("LNBITS_BACKEND_WALLET_CLASS"):
- funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
+ # if getenv("LNBITS_DATA_FOLDER"):
+ # data_folder = getenv("LNBITS_DATA_FOLDER")
+
+ # if getenv("LNBITS_DISABLED_EXTENSIONS"):
+ # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+
+ # if getenv("LNBITS_FORCE_HTTPS"):
+ # force_https = getenv("LNBITS_FORCE_HTTPS")
+
+ # if getenv("LNBITS_SERVICE_FEE"):
+ # service_fee = getenv("LNBITS_SERVICE_FEE")
+
+ # if getenv("LNBITS_DENOMINATION"):
+ # denomination = getenv("LNBITS_DENOMINATION", "sats")
+
+ # if getenv("LNBITS_BACKEND_WALLET_CLASS"):
+ # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin (
- "user" TEXT,
+ "user" TEXT PRIMARY KEY,
+ admin_users TEXT,
+ allowed_users TEXT,
+ admin_ext TEXT,
+ disabled_ext TEXT,
+ funding_source TEXT,
+ data_folder TEXT,
+ database_url TEXT,
+ force_https BOOLEAN,
+ service_fee REAL,
+ hide_api BOOLEAN,
+ denomination TEXT,
site_title TEXT,
site_tagline TEXT,
site_description TEXT,
- admin_users TEXT,
- allowed_users TEXT,
default_wallet_name TEXT,
- data_folder TEXT,
- disabled_ext TEXT,
- force_https BOOLEAN,
- service_fee REAL,
- funding_source TEXT
+ theme TEXT,
+ ad_space TEXT
);
"""
)
await db.execute(
"""
- INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- user.strip(),
+ INSERT INTO admin (
+ "user",
+ admin_users,
+ allowed_users,
+ admin_ext,
+ disabled_ext,
+ funding_source,
+ data_folder,
+ database_url,
+ force_https,
+ service_fee,
+ hide_api,
+ denomination,
site_title,
site_tagline,
site_description,
- admin_users[1:],
- allowed_users,
default_wallet_name,
- data_folder,
+ theme,
+ ad_space)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ user,
+ admin_users,
+ allowed_users,
+ admin_ext,
disabled_ext,
+ funding_source,
+ data_folder,
+ database_url,
force_https,
service_fee,
- funding_source,
+ hide_api,
+ denomination,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ theme,
+ ad_space,
),
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 4080ff018..f7c64de55 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -2,7 +2,7 @@ from sqlite3 import Row
from typing import List, Optional
from fastapi import Query
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
class UpdateAdminSettings(BaseModel):
@@ -19,18 +19,27 @@ class UpdateAdminSettings(BaseModel):
funding_source: Optional[str]
class Admin(BaseModel):
+ # users
user: str
+ admin_users: Optional[str]
+ allowed_users: Optional[str]
+ admin_ext: Optional[str]
+ disabled_ext: Optional[str]
+ funding_source: Optional[str]
+ # ops
+ data_folder: Optional[str]
+ database_url: Optional[str]
+ force_https: bool = Field(default=True)
+ service_fee: float = Field(default=0)
+ hide_api: bool = Field(default=False)
+ # Change theme
site_title: Optional[str]
site_tagline: Optional[str]
site_description: Optional[str]
- allowed_users: Optional[str]
- admin_users: str
- default_wallet_name: str
- data_folder: str
- disabled_ext: str
- force_https: Optional[bool] = Query(True)
- service_fee: float
- funding_source: str
+ default_wallet_name: Optional[str]
+ denomination: str = Field(default="sats")
+ theme: Optional[str]
+ ad_space: Optional[str]
@classmethod
def from_row(cls, row: Row) -> "Admin":
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 00a0c99fc..105f05a10 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -19,15 +19,17 @@ templates = Jinja2Templates(directory="templates")
@admin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
- print(g())
funding = [f.dict() for f in await get_funding()]
-
+ error, balance = await g().WALLET.status()
print("ADMIN", admin.dict())
+ print(g().admin_conf)
return admin_renderer().TemplateResponse(
"admin/index.html", {
"request": request,
"user": user.dict(),
"admin": admin.dict(),
- "funding": funding
+ "funding": funding,
+ "settings": g().admin_conf.dict(),
+ "balance": balance
}
)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 43cb87cbe..ed5c77f7e 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -4,7 +4,7 @@ from email.policy import default
from os import path
from typing import List
-from environs import Env # type: ignore
+from environs import Env
env = Env()
env.read_env()
From a72ed98997656a709b00d171c27c387c26dfe0bd Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:28:07 +0000
Subject: [PATCH 054/134] topup wallet endpoint
---
lnbits/extensions/admin/crud.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 6fccb8ee6..683558f94 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,26 +1,31 @@
+import json
from typing import List, Optional
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import *
+from lnbits.tasks import internal_invoice_queue
from . import db
from .models import Admin, Funding
-def update_wallet_balance(wallet_id: str, amount: int) -> str:
+async def update_wallet_balance(wallet_id: str, amount: int) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
- create_payment(
+
+ payment = await create_payment(
wallet_id=wallet_id,
checking_id=internal_id,
payment_request="admin_internal",
payment_hash="admin_internal",
- amount=amount * 1000,
+ amount=amount*1000,
memo="Admin top up",
pending=False,
)
- return "success"
+ # manually send this for now
+ await internal_invoice_queue.put(internal_id)
+ return payment
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
From 42a9af986ab3620bdb61270961c6adbda4a66c95 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:28:44 +0000
Subject: [PATCH 055/134] update admin settings in db
---
lnbits/extensions/admin/models.py | 29 +++++++++++++++++-----------
lnbits/extensions/admin/views_api.py | 8 ++++----
2 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index f7c64de55..36d9b8152 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,17 +6,24 @@ from pydantic import BaseModel, Field
class UpdateAdminSettings(BaseModel):
- site_title: Optional[str]
- site_tagline: Optional[str]
- site_description: Optional[str]
- allowed_users: Optional[str]
- admin_users: Optional[str]
- default_wallet_name: Optional[str]
- data_folder: Optional[str]
- disabled_ext: Optional[str]
- force_https: Optional[bool]
- service_fee: Optional[float]
- funding_source: Optional[str]
+ # users
+ admin_users: str = Query(None)
+ allowed_users: str = Query(None)
+ admin_ext: str = Query(None)
+ disabled_ext: str = Query(None)
+ funding_source: str = Query(None)
+ # ops
+ force_https: bool = Query(None)
+ service_fee: float = Query(None, ge=0)
+ hide_api: bool = Query(None)
+ # Change theme
+ site_title: str = Query(None)
+ site_tagline: str = Query(None)
+ site_description: str = Query(None)
+ default_wallet_name: str = Query(None)
+ denomination: str = Query(None)
+ theme: str = Query(None)
+ ad_space: str = Query(None)
class Admin(BaseModel):
# users
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index b2c65be25..cb526aa54 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -12,16 +12,16 @@ from .crud import get_admin, update_admin, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
-async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)):
- print(g.wallet)
+async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)):
try:
wallet = await get_wallet(wallet_id)
except:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
- print(wallet)
- print(topup_amount)
+
+ await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
+
return {"status": "Success"}
From 2ebb4448d5ff8daa3b1c4729ce33dd4c5c744c7b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:29:18 +0000
Subject: [PATCH 056/134] update settings and topup logic
---
.../admin/templates/admin/index.html | 91 ++++++++++++-------
1 file changed, 57 insertions(+), 34 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 65ac9f336..e9ddc7c47 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -30,7 +30,7 @@
-
+
@@ -61,7 +61,7 @@
@@ -577,7 +577,6 @@
Site Description
s !== ad)
},
- topupWallet: function () {
- var self = this
+ topupWallet() {
LNbits.api
.request(
'GET',
'/admin/api/v1/admin/' +
- self.wallet.id +
+ this.wallet.data.id +
'/' +
- self.wallet.data.amount,
- self.g.user.wallets[0].adminkey
+ this.wallet.data.amount,
+ this.g.user.wallets[0].adminkey
)
- .then(function (response) {
- self.$q.notify({
+ .then((response) => {
+ this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- self.wallet.amount +
- ' to ' +
- self.wallet.id,
+ 'Success! Added ' +
+ this.wallet.data.amount +
+ ' to ' +
+ this.wallet.data.id,
icon: null
})
+ this.wallet.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -1224,36 +1224,59 @@
self.data.admin.edited.push(source)
console.log(self.data.admin.edited)
},
- UpdateLNbits: function () {
- var self = this
- let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin
+ UpdateLNbits() {
+ let {
+ admin_users,
+ allowed_users,
+ admin_ext,
+ disabled_ext,
+ funding_source,
+ force_https,
+ service_fee,
+ hide_api,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ denomination,
+ theme,
+ ad_space
+ } = this.data.admin
+ //console.log("this", this.data.admin)
let data = {
- site_title,
- site_tagline: this.data.admin.tagline,
- site_description: this.data.admin.description,
- admin_users: admin_users.toString(),
- default_wallet_name,
- data_folder,
+ admin_users: admin_users.toString(),
+ allowed_users: allowed_users.toString(),
+ admin_ext: admin_ext.toString(),
disabled_ext: disabled_ext.toString(),
- service_fee,
- funding_source: funding_source_primary}
+ funding_source,
+ force_https,
+ service_fee,
+ hide_api,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ denomination,
+ theme: theme.toString(),
+ ad_space: ad_space.toString()
+ }
console.log(data)
LNbits.api
.request(
'POST',
'/admin/api/v1/admin/',
- self.g.user.wallets[0].adminkey,
+ this.g.user.wallets[0].adminkey,
data
)
- .then(function (response) {
+ .then(response => {
console.log(response.data)
- self.$q.notify({
+ this.$q.notify({
type: 'positive',
message:
'Success! Added ' +
- self.wallet.amount +
+ this.wallet.amount +
' to ' +
- self.wallet.id,
+ this.wallet.id,
icon: null
})
})
From 3082a393436b88a24d860f87983560e758a02f66 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:34:47 +0000
Subject: [PATCH 057/134] make removeEmptyString fn as helper fn
---
lnbits/app.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 1ffedb54c..c8f5c60a4 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -26,6 +26,7 @@ from .helpers import (
get_css_vendored,
get_js_vendored,
get_valid_extensions,
+ removeEmptyString,
template_renderer,
url_for_vendored,
)
@@ -112,9 +113,6 @@ def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
- def removeEmptyString(arr):
- return list(filter(None, arr))
-
while True:
admin_set = await get_admin_settings()
if admin_set :
From c419bd27ebcd212da37f27994e630df08bdba7d4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:35:15 +0000
Subject: [PATCH 058/134] add some defaults
---
lnbits/config.py | 6 +++---
lnbits/extensions/admin/models.py | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 02e8cf537..b2fbfff12 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -33,10 +33,10 @@ class Settings(BaseSettings):
hide_api: bool = Field(default=False, env="LNBITS_HIDE_API")
denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
# Change theme
- site_title: str = Field(default=None, env="LNBITS_SITE_TITLE")
- site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE")
+ site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE")
+ site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
- default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME")
+ default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 36d9b8152..0f25679dc 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -17,11 +17,11 @@ class UpdateAdminSettings(BaseModel):
service_fee: float = Query(None, ge=0)
hide_api: bool = Query(None)
# Change theme
- site_title: str = Query(None)
- site_tagline: str = Query(None)
+ site_title: str = Query("LNbits")
+ site_tagline: str = Query("free and open-source lightning wallet")
site_description: str = Query(None)
- default_wallet_name: str = Query(None)
- denomination: str = Query(None)
+ default_wallet_name: str = Query("LNbits wallet")
+ denomination: str = Query("sats")
theme: str = Query(None)
ad_space: str = Query(None)
From 0897d0476356d1956711fc0f1c6448fbb88275fb Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:36:04 +0000
Subject: [PATCH 059/134] removeEmtpy sting as helper fn
---
lnbits/helpers.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e213240cd..e456f7150 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -154,8 +154,20 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
url = f"{base}{endpoint}{url_params}"
return url
+def removeEmptyString(arr):
+ return list(filter(None, arr))
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
+ if(settings.LNBITS_ADMIN_UI):
+ _ = g().admin_conf
+ settings.LNBITS_AD_SPACE = _.ad_space
+ settings.LNBITS_HIDE_API = _.hide_api
+ settings.LNBITS_SITE_TITLE = _.site_title
+ settings.LNBITS_DENOMINATION = _.denomination
+ settings.LNBITS_SITE_TAGLINE = _.site_tagline
+ settings.LNBITS_SITE_DESCRIPTION = _.site_description
+ settings.LNBITS_THEME_OPTIONS = _.theme
+
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
From 8c93aa304f0d484e908cd40c0a851b95fbbfd992 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:41:11 +0000
Subject: [PATCH 060/134] cleanup
---
lnbits/extensions/admin/crud.py | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 683558f94..e14ad1949 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,9 +1,7 @@
-import json
-from typing import List, Optional
+from typing import List
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import *
from lnbits.tasks import internal_invoice_queue
from . import db
@@ -37,14 +35,6 @@ async def update_admin(user: str, **kwargs) -> Admin:
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
-# async def update_admin(user: str, **kwargs) -> Optional[Admin]:
-# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
-# await db.execute(
-# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user)
-# )
-# new_settings = await get_admin()
-# return new_settings
-
async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
From 5ce73697d47aa6eb96bb6d69c7da1e4521bc4943 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:42:28 +0000
Subject: [PATCH 061/134] make string to list
---
lnbits/extensions/admin/views_api.py | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index cb526aa54..1d4e6a9c7 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,5 +1,6 @@
from http import HTTPStatus
+# from config import conf
from fastapi import Body, Depends, Request
from starlette.exceptions import HTTPException
@@ -7,6 +8,8 @@ from lnbits.core.crud import get_wallet
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+from lnbits.helpers import removeEmptyString
+from lnbits.requestvars import g
from .crud import get_admin, update_admin, update_wallet_balance
@@ -19,7 +22,7 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
-
+
await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
return {"status": "Success"}
@@ -29,14 +32,24 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
async def api_update_admin(
request: Request,
data: UpdateAdminSettings = Body(...),
- g: WalletTypeInfo = Depends(require_admin_key)
+ w: WalletTypeInfo = Depends(require_admin_key)
):
admin = await get_admin()
print(data)
- if not admin.user == g.wallet.user:
+ if not admin.user == w.wallet.user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
- updated = await update_admin(user=g.wallet.user, **data.dict())
- print(updated)
+ updated = await update_admin(user=w.wallet.user, **data.dict())
+
+ updated.admin_users = removeEmptyString(updated.admin_users.split(','))
+ updated.allowed_users = removeEmptyString(updated.allowed_users.split(','))
+ updated.admin_ext = removeEmptyString(updated.admin_ext.split(','))
+ updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(','))
+ updated.theme = removeEmptyString(updated.theme.split(','))
+ updated.ad_space = removeEmptyString(updated.ad_space.split(','))
+
+ g().admin_conf = g().admin_conf.copy(update=updated.dict())
+
+ print(g().admin_conf)
return {"status": "Success"}
From c299927e7ca1e16c8bc29a98b5af38ed402496de Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:42:47 +0000
Subject: [PATCH 062/134] success message
---
lnbits/extensions/admin/templates/admin/index.html | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index e9ddc7c47..9aa4f12a8 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1273,10 +1273,7 @@
this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- this.wallet.amount +
- ' to ' +
- this.wallet.id,
+ 'Success! Settings changed!',
icon: null
})
})
From 2a63fb191423b2f23de7f70e8197109a290f3689 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 14 Apr 2022 10:42:26 +0100
Subject: [PATCH 063/134] allow html to be passed to description
---
lnbits/core/templates/core/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html
index 68a7b7ede..146fc6ade 100644
--- a/lnbits/core/templates/core/index.html
+++ b/lnbits/core/templates/core/index.html
@@ -82,7 +82,7 @@
>
- {{SITE_DESCRIPTION}}
+ {{SITE_DESCRIPTION | safe}}
From 3fbdac127adf9459d8af4453a802248f7c3d94fc Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 14 Apr 2022 16:37:13 +0100
Subject: [PATCH 064/134] update funding wallets
---
lnbits/extensions/admin/crud.py | 12 +
.../admin/templates/admin/index.html | 523 ++++++++++--------
lnbits/extensions/admin/views.py | 3 +-
lnbits/extensions/admin/views_api.py | 19 +-
4 files changed, 315 insertions(+), 242 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index e14ad1949..dd39e8e42 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -39,6 +39,18 @@ async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
+async def update_funding(data: Funding) -> Funding:
+ await db.execute(
+ """
+ UPDATE funding
+ SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
+ WHERE id = ?
+ """,
+ (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
+ )
+ row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,))
+ assert row, "Newly updated settings couldn't be retrieved"
+ return Funding(**row) if row else None
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM funding")
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 9aa4f12a8..d56b3d793 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -31,11 +31,11 @@
-
-
-
- Wallets Management
-
+
+
+
+ Wallets Management
+
@@ -62,43 +62,96 @@
-
TopUp a wallet
-
-
-
-
-
-
-
-
+
TopUp a wallet
+
Funding Sources
-
+ {% raw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+ User Management
+
+
+ Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %}
+
+
Admin Users
+ hint="Users with admin privileges"
+ >
{% raw %}
-
-
-
- {% raw %}
-
Allowed Users
+
- {{ user }}
-
- {% endraw %}
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
+
+
+ Server Management
-
-
-
-
-
Disabled Extensions
-
-
-
-
-
- Save
-
-
-
-
-
- Server Management
-
Server Info
{%raw%}
- - SQlite: {{data.admin.data_folder}}
- - Postgres: {{data.admin.database_url}}
+ -
+ SQlite: {{data.admin.data_folder}}
+
+ -
+ Postgres: {{data.admin.database_url}}
+
{%endraw%}
@@ -520,7 +576,10 @@
Hide API
- Hides wallet api, extensions can choose to honor
+ Hides wallet api, extensions can choose to
+ honor
-
-
-
- Save
-
-
-
-
-
- UI Management
-
+
+
+
+ Save
+
+
+
+
+
+ UI Management
+
+
Default Wallet Name
@@ -628,12 +682,15 @@
@keydown.enter="addAdSpace"
type="text"
label="Ad image URL"
- hint="Ad image filepaths or urls, extensions can choose to honor">
+ hint="Ad image filepaths or urls, extensions can choose to honor"
+ >
{% raw %}
-
-
-
-
- Save
-
-
-
-
-
+
+
+
+ Save
+
+
+
+
+
-
-
-Admin
-
+
+
-
-
-
-
- Wallet topup
-
-
-
-
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }}
@@ -1100,14 +1114,22 @@
'LnbitsWallet',
'OpenNodeWallet'
],
-
+
admin: {
edited: [],
- funding: {},
+ funding: [],
senddata: {}
}
},
- themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'],
+ themes: [
+ 'classic',
+ 'bitcoin',
+ 'flamingo',
+ 'mint',
+ 'autumn',
+ 'monochrome',
+ 'salvador'
+ ],
options: [
'bleskomat',
'captcha',
@@ -1139,10 +1161,13 @@
self.cancel.on = true
}
funding = JSON.parse(String('{{ funding | tojson|safe }}'))
- var i
+ funding.map(f => {
+ this.data.admin.funding.push(f)
+ })
+ /*var i
for (i = 0; i < funding.length; i++) {
self.data.admin.funding[funding[i].backend_wallet] = funding[i]
- }
+ }*/
let settings = JSON.parse('{{ settings | tojson|safe }}')
settings.balance = '{{ balance }}'
this.data.admin = {...this.data.admin, ...settings}
@@ -1150,42 +1175,42 @@
console.log(settings)
},
methods: {
- addAdminUser(){
+ addAdminUser() {
let addUser = this.data.admin_users_add
let admin_users = this.data.admin.admin_users
- if(addUser.length && !admin_users.includes(addUser)){
+ if (addUser.length && !admin_users.includes(addUser)) {
admin_users.push(addUser)
this.data.admin.admin_users = admin_users
- this.data.admin_users_add = ""
+ this.data.admin_users_add = ''
}
},
- removeAdminUser(user){
+ removeAdminUser(user) {
let admin_users = this.data.admin.admin_users
this.data.admin.admin_users = admin_users.filter(u => u !== user)
},
- addAllowedUser(){
+ addAllowedUser() {
let addUser = this.data.allowed_users_add
let allowed_users = this.data.admin.allowed_users
- if(addUser.length && !allowed_users.includes(addUser)){
+ if (addUser.length && !allowed_users.includes(addUser)) {
allowed_users.push(addUser)
this.data.admin.allowed_users = allowed_users
- this.data.allowed_users_add = ""
+ this.data.allowed_users_add = ''
}
},
- removeAllowedUser(user){
+ removeAllowedUser(user) {
let allowed_users = this.data.admin.allowed_users
this.data.admin.allowed_users = allowed_users.filter(u => u !== user)
},
- addAdSpace(){
+ addAdSpace() {
let adSpace = this.data.ad_space_add
let spaces = this.data.admin.ad_space
- if(adSpace.length && !spaces.includes(adSpace)){
+ if (adSpace.length && !spaces.includes(adSpace)) {
spaces.push(adSpace)
this.data.admin.ad_space = spaces
- this.data.ad_space_add = ""
+ this.data.ad_space_add = ''
}
},
- removeAdSpace(ad){
+ removeAdSpace(ad) {
let spaces = this.data.admin.ad_space
this.data.admin.ad_space = spaces.filter(s => s !== ad)
},
@@ -1199,14 +1224,14 @@
this.wallet.data.amount,
this.g.user.wallets[0].adminkey
)
- .then((response) => {
+ .then(response => {
this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- this.wallet.data.amount +
- ' to ' +
- this.wallet.data.id,
+ 'Success! Added ' +
+ this.wallet.data.amount +
+ ' to ' +
+ this.wallet.data.id,
icon: null
})
this.wallet.data = {}
@@ -1224,6 +1249,29 @@
self.data.admin.edited.push(source)
console.log(self.data.admin.edited)
},
+ updateFunding(fund) {
+ let data = this.data.admin.funding.find(v => v.backend_wallet == fund)
+
+ LNbits.api
+ .request(
+ 'POST',
+ '/admin/api/v1/admin/funding',
+ this.g.user.wallets[0].adminkey,
+ data
+ )
+ .then(response => {
+ //let wallet = response.data.backend_wallet
+ //this.data.admin.funding[wallet] = response.data
+ //this.data.admin.funding[wallet].endpoint = response.data.endpoint
+ //console.log(this.data.admin.funding)
+ //console.log(this.data.admin)
+ this.$q.notify({
+ type: 'positive',
+ message: `Success! ${response.data.backend_wallet} changed!`,
+ icon: null
+ })
+ })
+ },
UpdateLNbits() {
let {
admin_users,
@@ -1272,8 +1320,7 @@
console.log(response.data)
this.$q.notify({
type: 'positive',
- message:
- 'Success! Settings changed!',
+ message: 'Success! Settings changed!',
icon: null
})
})
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 105f05a10..24b8ca859 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -21,8 +21,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
funding = [f.dict() for f in await get_funding()]
error, balance = await g().WALLET.status()
- print("ADMIN", admin.dict())
- print(g().admin_conf)
+
return admin_renderer().TemplateResponse(
"admin/index.html", {
"request": request,
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 1d4e6a9c7..b797dc2d1 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.admin import admin_ext
-from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+from lnbits.extensions.admin.models import Admin, Funding, UpdateAdminSettings
from lnbits.helpers import removeEmptyString
from lnbits.requestvars import g
-from .crud import get_admin, update_admin, update_wallet_balance
+from .crud import get_admin, update_admin, update_funding, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
@@ -53,3 +53,18 @@ async def api_update_admin(
print(g().admin_conf)
return {"status": "Success"}
+
+@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
+async def api_update_funding(
+ request: Request,
+ data: Funding = Body(...),
+ w: WalletTypeInfo = Depends(require_admin_key)
+ ):
+ admin = await get_admin()
+
+ if not admin.user == w.wallet.user:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
+ funding = await update_funding(data=data)
+ return funding
From a07fbf0187c72b45e0e102951faf3ed6cbfb75fc Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 18 Apr 2022 14:25:06 +0100
Subject: [PATCH 065/134] allow user settings without restart
---
lnbits/core/views/generic.py | 7 ++++++-
lnbits/decorators.py | 8 ++++++++
lnbits/helpers.py | 3 +++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 31a7b0300..83648c444 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -15,7 +15,9 @@ from lnbits.core import db
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for
+from lnbits.requestvars import g
from lnbits.settings import (
+ LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
LNBITS_CUSTOM_LOGO,
@@ -37,7 +39,6 @@ from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
-
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
@@ -119,6 +120,10 @@ async def wallet(
wallet_name = nme
service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+
if not user_id:
user = await get_user((await create_account()).id)
logger.info(f"Create user {user.id}") # type: ignore
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index d4aa63aea..f951163f8 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -16,6 +16,7 @@ from lnbits.core.models import User, Wallet
from lnbits.requestvars import g
from lnbits.settings import (
LNBITS_ADMIN_EXTENSIONS,
+ LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
)
@@ -138,6 +139,9 @@ async def get_key_type(
detail="Invoice (or Admin) key required.",
)
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+
for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
):
@@ -231,6 +235,10 @@ async def check_user_exists(usr: UUID4) -> User:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
+
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException(
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e456f7150..1167143f8 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -24,6 +24,9 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
+ if settings.LNBITS_ADMIN_UI:
+ settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext
+ settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
self._admin_only: List[str] = [
x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS
From 931286b476bed767e3900a3b60cf10ab962c29e1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 18 Apr 2022 14:25:26 +0100
Subject: [PATCH 066/134] advert for server restart option
---
lnbits/extensions/admin/templates/admin/index.html | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d56b3d793..089c5f1ce 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -51,7 +51,9 @@
-
Active Funding
+
+ Active Funding (Requires server restart)
+
Date: Thu, 21 Apr 2022 11:08:26 +0100
Subject: [PATCH 067/134] cleanup prints and console logs
---
lnbits/extensions/admin/crud.py | 2 +-
lnbits/extensions/admin/templates/admin/index.html | 4 ++--
lnbits/extensions/admin/views_api.py | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index dd39e8e42..f866bc1aa 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -27,7 +27,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- print("UPDATE", q)
+ # print("UPDATE", q)
await db.execute(
f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 089c5f1ce..584d3a33f 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1310,7 +1310,7 @@
theme: theme.toString(),
ad_space: ad_space.toString()
}
- console.log(data)
+ //console.log(data)
LNbits.api
.request(
'POST',
@@ -1319,7 +1319,7 @@
data
)
.then(response => {
- console.log(response.data)
+ //console.log(response.data)
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index b797dc2d1..c0650c8a9 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -35,7 +35,7 @@ async def api_update_admin(
w: WalletTypeInfo = Depends(require_admin_key)
):
admin = await get_admin()
- print(data)
+ # print(data)
if not admin.user == w.wallet.user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
@@ -51,7 +51,7 @@ async def api_update_admin(
g().admin_conf = g().admin_conf.copy(update=updated.dict())
- print(g().admin_conf)
+ # print(g().admin_conf)
return {"status": "Success"}
@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
From 3f38a9094b9c9e5e3c6b29df67b3508efbc41be6 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 10:49:21 +0100
Subject: [PATCH 068/134] create first user on fresh install
---
.env.example | 4 ++--
lnbits/config.py | 3 ++-
lnbits/extensions/admin/README.md | 15 ++++++++-------
lnbits/extensions/admin/migrations.py | 15 ++++++++++++++-
4 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/.env.example b/.env.example
index 4192f82ec..f0e21aa86 100644
--- a/.env.example
+++ b/.env.example
@@ -4,8 +4,8 @@ PORT=5000
DEBUG=false
LNBITS_ADMIN_USERS="" # User IDs seperated by comma
-LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS
+LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
diff --git a/lnbits/config.py b/lnbits/config.py
index b2fbfff12..3ce51c3cb 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -19,6 +19,7 @@ def list_parse_fallback(v):
return v.replace(' ','').split(',')
class Settings(BaseSettings):
+ admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI")
# users
admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
@@ -37,7 +38,7 @@ class Settings(BaseSettings):
site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
- theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
+ theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
env: Optional[str]
diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md
index 277294592..6cf073a11 100644
--- a/lnbits/extensions/admin/README.md
+++ b/lnbits/extensions/admin/README.md
@@ -1,11 +1,12 @@
-Example Extension
-*tagline*
-This is an example extension to help you organise and build you own.
+# Admin Extension
-Try to include an image
-
+## Dashboard to manage LNbits from the UI
+With AdminUI you can manage your LNbits from the UI
-If your extension has API endpoints, include useful ones here
+
-curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
+## Before you start
+
+**This extension doesn't discard the need for the `.env` file!**
+In the .env file, set the `LNBITS_ADMIN_USERS` variable to include at least your user id.
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 574f772d1..0e22e6677 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,9 +6,22 @@ from lnbits.config import conf
from lnbits.helpers import urlsafe_short_hash
+async def get_admin_user():
+ if(conf.admin_users[0]):
+ return conf.admin_users[0]
+ from lnbits.core.crud import create_account, get_user
+ print("Seems like there's no admin users yet. Let's create an account for you!")
+ account = await create_account()
+ user = account.id
+ assert user, "Newly created user couldn't be retrieved"
+ print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ return user
+
+
+
async def m001_create_admin_table(db):
# users/server
- user = conf.admin_users[0]
+ user = await get_admin_user()
admin_users = ",".join(conf.admin_users)
allowed_users = ",".join(conf.allowed_users)
admin_ext = ",".join(conf.admin_ext)
From 0a29fb736093aeca3987122a7a78381b6f760499 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 12:29:58 +0100
Subject: [PATCH 069/134] fix schemas for admin
---
lnbits/commands.py | 11 ++++++++---
lnbits/extensions/admin/crud.py | 12 ++++++------
lnbits/extensions/admin/migrations.py | 26 +++++++++++++-------------
3 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 8c39c338f..7d9b49e21 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -55,8 +55,12 @@ def bundle_vendored():
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
- async with core_db.connect() as conn:
+ try:
+ ext_db = importlib.import_module(f"lnbits.extensions.admin").db
+ except:
+ return False
+ async with ext_db.connect() as conn:
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
@@ -65,11 +69,12 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
+ print("EXISTS", exists)
if not exists:
return False
- row = await conn.fetchone("SELECT * from admin")
-
+ row = await conn.fetchone("SELECT * from admin.admin")
+
return Admin(**row) if row else None
async def migrate_databases():
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index f866bc1aa..67fbc6143 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -29,30 +29,30 @@ async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
await db.execute(
- f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
+ f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,))
+ row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,))
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
async def get_admin() -> Admin:
- row = await db.fetchone("SELECT * FROM admin")
+ row = await db.fetchone("SELECT * FROM admin.admin")
return Admin(**row) if row else None
async def update_funding(data: Funding) -> Funding:
await db.execute(
"""
- UPDATE funding
+ UPDATE admin.funding
SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
WHERE id = ?
""",
(data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
)
- row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,))
+ row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,))
assert row, "Newly updated settings couldn't be retrieved"
return Funding(**row) if row else None
async def get_funding() -> List[Funding]:
- rows = await db.fetchall("SELECT * FROM funding")
+ rows = await db.fetchall("SELECT * FROM admin.funding")
return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 0e22e6677..c94d140b7 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -96,7 +96,7 @@ async def m001_create_admin_table(db):
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS admin (
+ CREATE TABLE IF NOT EXISTS admin.admin (
"user" TEXT PRIMARY KEY,
admin_users TEXT,
allowed_users TEXT,
@@ -120,7 +120,7 @@ async def m001_create_admin_table(db):
)
await db.execute(
"""
- INSERT INTO admin (
+ INSERT INTO admin.admin (
"user",
admin_users,
allowed_users,
@@ -171,7 +171,7 @@ async def m001_create_funding_table(db):
# Make the funding table, if it does not already exist
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS funding (
+ CREATE TABLE IF NOT EXISTS admin.funding (
id TEXT PRIMARY KEY,
backend_wallet TEXT,
endpoint TEXT,
@@ -188,7 +188,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, selected)
VALUES (?, ?, ?, ?)
""",
(
@@ -200,7 +200,7 @@ async def m001_create_funding_table(db):
)
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -214,7 +214,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -228,7 +228,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
@@ -244,7 +244,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
@@ -259,7 +259,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
@@ -274,7 +274,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -288,7 +288,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -302,7 +302,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -317,7 +317,7 @@ async def m001_create_funding_table(db):
## PLACEHOLDER FOR ECLAIR WALLET
# await db.execute(
# """
- # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
# VALUES (?, ?, ?, ?, ?)
# """,
# (
From ac74cfaab7e70d8df8229241e040403d473f04e1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 12:53:47 +0100
Subject: [PATCH 070/134] fix sqlite and show user account
---
lnbits/app.py | 1 +
lnbits/commands.py | 2 +-
lnbits/extensions/admin/migrations.py | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index c8f5c60a4..2b4837584 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -116,6 +116,7 @@ def check_settings(app: FastAPI):
while True:
admin_set = await get_admin_settings()
if admin_set :
+ print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
break
print("ERROR:", admin_set)
await asyncio.sleep(5)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 7d9b49e21..763a5b90b 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -69,7 +69,7 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
- print("EXISTS", exists)
+
if not exists:
return False
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index c94d140b7..6c5b507d1 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -15,6 +15,7 @@ async def get_admin_user():
user = account.id
assert user, "Newly created user couldn't be retrieved"
print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ conf.admin_users.insert(0, user)
return user
From 1ebd557b1d544f6baab770111a4bf89f31abea66 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 15:35:04 +0100
Subject: [PATCH 071/134] cleanup and info to user on startup
---
lnbits/app.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 2b4837584..eaa331364 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -112,13 +112,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
-
while True:
admin_set = await get_admin_settings()
if admin_set :
- print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
break
- print("ERROR:", admin_set)
+ print("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
@@ -128,6 +126,7 @@ def check_settings(app: FastAPI):
admin_set.theme = removeEmptyString(admin_set.theme.split(','))
admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
g().admin_conf = conf.copy(update=admin_set.dict())
+ print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
From e04e24faec18e01c306d86a6bd45f238a74e1d38 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 8 Jun 2022 11:00:43 +0100
Subject: [PATCH 072/134] add custom logo
---
lnbits/config.py | 1 +
lnbits/extensions/admin/migrations.py | 58 ++-----------------
lnbits/extensions/admin/models.py | 2 +
.../admin/templates/admin/index.html | 20 +++++--
lnbits/helpers.py | 3 +-
5 files changed, 26 insertions(+), 58 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 3ce51c3cb..d07ca044e 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -39,6 +39,7 @@ class Settings(BaseSettings):
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
+ custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
env: Optional[str]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 6c5b507d1..aad66f02a 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -41,60 +41,9 @@ async def m001_create_admin_table(db):
site_description = conf.site_description
default_wallet_name = conf.default_wallet_name
theme = ",".join(conf.theme)
+ custom_logo = conf.custom_logo
ad_space = ",".join(conf.ad_space)
- # if getenv("LNBITS_ADMIN_EXTENSIONS"):
- # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS")
-
- # if getenv("LNBITS_DATABASE_URL"):
- # database_url = getenv("LNBITS_DATABASE_URL")
-
- # if getenv("LNBITS_HIDE_API"):
- # hide_api = getenv("LNBITS_HIDE_API")
-
- # if getenv("LNBITS_THEME_OPTIONS"):
- # theme = getenv("LNBITS_THEME_OPTIONS")
-
- # if getenv("LNBITS_AD_SPACE"):
- # ad_space = getenv("LNBITS_AD_SPACE")
-
- # if getenv("LNBITS_SITE_TITLE"):
- # site_title = getenv("LNBITS_SITE_TITLE")
-
- # if getenv("LNBITS_SITE_TAGLINE"):
- # site_tagline = getenv("LNBITS_SITE_TAGLINE")
-
- # if getenv("LNBITS_SITE_DESCRIPTION"):
- # site_description = getenv("LNBITS_SITE_DESCRIPTION")
-
- # if getenv("LNBITS_ALLOWED_USERS"):
- # allowed_users = getenv("LNBITS_ALLOWED_USERS")
-
- # if getenv("LNBITS_ADMIN_USERS"):
- # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
- # user = admin_users.split(',')[0]
-
- # if getenv("LNBITS_DEFAULT_WALLET_NAME"):
- # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
-
- # if getenv("LNBITS_DATA_FOLDER"):
- # data_folder = getenv("LNBITS_DATA_FOLDER")
-
- # if getenv("LNBITS_DISABLED_EXTENSIONS"):
- # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
-
- # if getenv("LNBITS_FORCE_HTTPS"):
- # force_https = getenv("LNBITS_FORCE_HTTPS")
-
- # if getenv("LNBITS_SERVICE_FEE"):
- # service_fee = getenv("LNBITS_SERVICE_FEE")
-
- # if getenv("LNBITS_DENOMINATION"):
- # denomination = getenv("LNBITS_DENOMINATION", "sats")
-
- # if getenv("LNBITS_BACKEND_WALLET_CLASS"):
- # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
-
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin.admin (
@@ -115,6 +64,7 @@ async def m001_create_admin_table(db):
site_description TEXT,
default_wallet_name TEXT,
theme TEXT,
+ custom_logo TEXT,
ad_space TEXT
);
"""
@@ -139,8 +89,9 @@ async def m001_create_admin_table(db):
site_description,
default_wallet_name,
theme,
+ custom_logo,
ad_space)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
user,
@@ -160,6 +111,7 @@ async def m001_create_admin_table(db):
site_description,
default_wallet_name,
theme,
+ custom_logo,
ad_space,
),
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 0f25679dc..3b17e720c 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -23,6 +23,7 @@ class UpdateAdminSettings(BaseModel):
default_wallet_name: str = Query("LNbits wallet")
denomination: str = Query("sats")
theme: str = Query(None)
+ custom_logo: str = Query(None)
ad_space: str = Query(None)
class Admin(BaseModel):
@@ -46,6 +47,7 @@ class Admin(BaseModel):
default_wallet_name: Optional[str]
denomination: str = Field(default="sats")
theme: Optional[str]
+ custom_logo: Optional[str]
ad_space: Optional[str]
@classmethod
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 584d3a33f..d9790051e 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -705,6 +705,19 @@
+
@@ -718,10 +731,7 @@
-
+
@@ -1292,6 +1323,8 @@
disabled_ext,
funding_source,
force_https,
+ reserve_fee_min,
+ reserve_fee_pct,
service_fee,
hide_api,
site_title,
@@ -1311,6 +1344,8 @@
disabled_ext: disabled_ext.toString(),
funding_source,
force_https,
+ reserve_fee_min,
+ reserve_fee_pct,
service_fee,
hide_api,
site_title,
diff --git a/lnbits/settings.py b/lnbits/settings.py
index ed5c77f7e..8e5c321a2 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,6 +1,5 @@
import importlib
import subprocess
-from email.policy import default
from os import path
from typing import List
From 929d174ba4bc20c074ff7bfd2741521c846d005b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 8 Jun 2022 15:38:28 +0100
Subject: [PATCH 074/134] calle's semantics
---
lnbits/extensions/admin/templates/admin/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 832629bcf..d34b90683 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -67,7 +67,7 @@
-
Minimum wallet reserve
+
Fee reserve
From 8e0baf7b2dcc2eb018ed972aba856ff3aca41b18 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 5 Jul 2022 16:25:02 +0100
Subject: [PATCH 075/134] blacked
---
lnbits/extensions/admin/__init__.py | 1 +
lnbits/extensions/admin/crud.py | 23 ++++++++++++---
lnbits/extensions/admin/migrations.py | 10 ++++---
lnbits/extensions/admin/models.py | 2 ++
lnbits/extensions/admin/views.py | 10 ++++---
lnbits/extensions/admin/views_api.py | 41 ++++++++++++++-------------
6 files changed, 56 insertions(+), 31 deletions(-)
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
index 6a56b2bb1..24b91fe22 100644
--- a/lnbits/extensions/admin/__init__.py
+++ b/lnbits/extensions/admin/__init__.py
@@ -7,6 +7,7 @@ db = Database("ext_admin")
admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"])
+
def admin_renderer():
return template_renderer(["lnbits/extensions/admin/templates"])
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 67fbc6143..0d7019ccf 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -11,13 +11,13 @@ from .models import Admin, Funding
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
-
+
payment = await create_payment(
wallet_id=wallet_id,
checking_id=internal_id,
payment_request="admin_internal",
payment_hash="admin_internal",
- amount=amount*1000,
+ amount=amount * 1000,
memo="Admin top up",
pending=False,
)
@@ -25,6 +25,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
await internal_invoice_queue.put(internal_id)
return payment
+
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
@@ -35,23 +36,37 @@ async def update_admin(user: str, **kwargs) -> Admin:
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
+
async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin.admin")
return Admin(**row) if row else None
+
async def update_funding(data: Funding) -> Funding:
await db.execute(
"""
UPDATE admin.funding
SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
WHERE id = ?
- """,
- (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
+ """,
+ (
+ data.backend_wallet,
+ data.endpoint,
+ data.port,
+ data.read_key,
+ data.invoice_key,
+ data.admin_key,
+ data.cert,
+ data.balance,
+ data.selected,
+ data.id,
+ ),
)
row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,))
assert row, "Newly updated settings couldn't be retrieved"
return Funding(**row) if row else None
+
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM admin.funding")
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index f3663435c..388f5ec64 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -7,19 +7,21 @@ from lnbits.helpers import urlsafe_short_hash
async def get_admin_user():
- if(conf.admin_users[0]):
+ if conf.admin_users[0]:
return conf.admin_users[0]
from lnbits.core.crud import create_account, get_user
+
print("Seems like there's no admin users yet. Let's create an account for you!")
account = await create_account()
user = account.id
assert user, "Newly created user couldn't be retrieved"
- print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ print(
+ f"Your newly created account/user id is: {user}. This will be the Super Admin user."
+ )
conf.admin_users.insert(0, user)
return user
-
async def m001_create_admin_table(db):
# users/server
user = await get_admin_user()
@@ -28,7 +30,7 @@ async def m001_create_admin_table(db):
admin_ext = ",".join(conf.admin_ext)
disabled_ext = ",".join(conf.disabled_ext)
funding_source = conf.funding_source
- #operational
+ # operational
data_folder = conf.data_folder
database_url = conf.database_url
force_https = conf.force_https
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 3d8efdcd8..6e95d68fb 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -28,6 +28,7 @@ class UpdateAdminSettings(BaseModel):
custom_logo: str = Query(None)
ad_space: str = Query(None)
+
class Admin(BaseModel):
# users
user: str
@@ -59,6 +60,7 @@ class Admin(BaseModel):
data = dict(row)
return cls(**data)
+
class Funding(BaseModel):
id: str
backend_wallet: str
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 24b8ca859..ceda5192a 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -16,19 +16,21 @@ from .crud import get_admin, get_funding
templates = Jinja2Templates(directory="templates")
+
@admin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
funding = [f.dict() for f in await get_funding()]
error, balance = await g().WALLET.status()
-
+
return admin_renderer().TemplateResponse(
- "admin/index.html", {
+ "admin/index.html",
+ {
"request": request,
"user": user.dict(),
"admin": admin.dict(),
"funding": funding,
"settings": g().admin_conf.dict(),
- "balance": balance
- }
+ "balance": balance,
+ },
)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c0650c8a9..784ad97fa 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -15,16 +15,18 @@ from .crud import get_admin, update_admin, update_funding, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
-async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)):
+async def api_update_balance(
+ wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)
+):
try:
wallet = await get_wallet(wallet_id)
except:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
-
+
return {"status": "Success"}
@@ -32,39 +34,40 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
async def api_update_admin(
request: Request,
data: UpdateAdminSettings = Body(...),
- w: WalletTypeInfo = Depends(require_admin_key)
- ):
+ w: WalletTypeInfo = Depends(require_admin_key),
+):
admin = await get_admin()
# print(data)
if not admin.user == w.wallet.user:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
updated = await update_admin(user=w.wallet.user, **data.dict())
- updated.admin_users = removeEmptyString(updated.admin_users.split(','))
- updated.allowed_users = removeEmptyString(updated.allowed_users.split(','))
- updated.admin_ext = removeEmptyString(updated.admin_ext.split(','))
- updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(','))
- updated.theme = removeEmptyString(updated.theme.split(','))
- updated.ad_space = removeEmptyString(updated.ad_space.split(','))
+ updated.admin_users = removeEmptyString(updated.admin_users.split(","))
+ updated.allowed_users = removeEmptyString(updated.allowed_users.split(","))
+ updated.admin_ext = removeEmptyString(updated.admin_ext.split(","))
+ updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(","))
+ updated.theme = removeEmptyString(updated.theme.split(","))
+ updated.ad_space = removeEmptyString(updated.ad_space.split(","))
g().admin_conf = g().admin_conf.copy(update=updated.dict())
-
+
# print(g().admin_conf)
return {"status": "Success"}
+
@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
async def api_update_funding(
request: Request,
data: Funding = Body(...),
- w: WalletTypeInfo = Depends(require_admin_key)
- ):
+ w: WalletTypeInfo = Depends(require_admin_key),
+):
admin = await get_admin()
if not admin.user == w.wallet.user:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
funding = await update_funding(data=data)
return funding
From 429217f5a4876920277ad0719458daca52045b90 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:28:13 +0100
Subject: [PATCH 076/134] Had to add a couple of tries
---
lnbits/core/views/generic.py | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 83648c444..63f7af681 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -133,12 +133,19 @@ async def wallet(
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
- if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
- return template_renderer().TemplateResponse(
- "error.html", {"request": request, "err": "User not authorized."}
- )
- if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
- user.admin = True
+ try:
+ if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
+ return template_renderer().TemplateResponse(
+ "error.html", {"request": request, "err": "User not authorized."}
+ )
+ except:
+ pass
+
+ try:
+ if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
+ user.admin = True
+ except:
+ pass
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
From 1aa2f01d29a25dd5fffac385afd3b5814e10b45c Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:31:31 +0100
Subject: [PATCH 077/134] Added couple more tries
---
lnbits/decorators.py | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index f951163f8..a810892d9 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -239,13 +239,16 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
-
- if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
-
- if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
- g().user.admin = True
-
+ try:
+ if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ except:
+ pass
+ try:
+ if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
+ g().user.admin = True
+ except:
+ pass
return g().user
From 10a065a7ca4f98c725e690d66f9c4f5c60d0c4e3 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:35:06 +0100
Subject: [PATCH 078/134] Reverted try
---
lnbits/decorators.py | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index a810892d9..904ca1c25 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -239,16 +239,12 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
- try:
- if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- except:
- pass
- try:
- if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
- g().user.admin = True
+ if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
+ g().user.admin = True
except:
pass
return g().user
From 3129692ab16fbd66727faba6fb04c87e56b6e0f7 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:37:07 +0100
Subject: [PATCH 079/134] reverted other try
---
lnbits/core/views/generic.py | 19 ++++++-------------
lnbits/decorators.py | 2 --
2 files changed, 6 insertions(+), 15 deletions(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 63f7af681..83648c444 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -133,19 +133,12 @@ async def wallet(
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
- try:
- if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
- return template_renderer().TemplateResponse(
- "error.html", {"request": request, "err": "User not authorized."}
- )
- except:
- pass
-
- try:
- if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
- user.admin = True
- except:
- pass
+ if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
+ return template_renderer().TemplateResponse(
+ "error.html", {"request": request, "err": "User not authorized."}
+ )
+ if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
+ user.admin = True
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 904ca1c25..dd26d8feb 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -245,6 +245,4 @@ async def check_user_exists(usr: UUID4) -> User:
)
if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
g().user.admin = True
- except:
- pass
return g().user
From 42f6acd9f4f2f076cf278a843d48db11cbfb2b63 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 21 Sep 2022 18:40:46 +0100
Subject: [PATCH 080/134] fix main merge missing settings
---
lnbits/app.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index eaa331364..176c6bb97 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -56,6 +56,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ g().admin_conf = conf
+ check_settings(app)
+
+ g().WALLET = WALLET
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
"/core/static",
From a6bdd8c575f74685c9f71dfd142c908543b1d210 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 10:46:11 +0200
Subject: [PATCH 081/134] format
---
lnbits/app.py | 22 +++++++++++++---------
lnbits/commands.py | 6 +++++-
lnbits/config.py | 25 ++++++++++++++++++-------
lnbits/core/views/generic.py | 1 +
lnbits/decorators.py | 2 +-
lnbits/helpers.py | 8 +++++---
6 files changed, 43 insertions(+), 21 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 176c6bb97..f4e44a0f6 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -114,24 +114,28 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
+
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
while True:
admin_set = await get_admin_settings()
- if admin_set :
+ if admin_set:
break
print("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
-
- admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
- admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(','))
- admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(','))
- admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(','))
- admin_set.theme = removeEmptyString(admin_set.theme.split(','))
- admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
+
+ admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(","))
+ admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(","))
+ admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(","))
+ admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(","))
+ admin_set.theme = removeEmptyString(admin_set.theme.split(","))
+ admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(","))
g().admin_conf = conf.copy(update=admin_set.dict())
- print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
+ print(
+ f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}"
+ )
+
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 763a5b90b..86868f1f7 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -5,6 +5,7 @@ import re
import warnings
import click
+from genericpath import exists
from loguru import logger
from .core import db as core_db
@@ -52,6 +53,7 @@ def bundle_vendored():
with open(outputpath, "w") as f:
f.write(output)
+
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
@@ -61,6 +63,7 @@ async def get_admin_settings():
return False
async with ext_db.connect() as conn:
+
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
@@ -69,7 +72,7 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
-
+
if not exists:
return False
@@ -77,6 +80,7 @@ async def get_admin_settings():
return Admin(**row) if row else None
+
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
diff --git a/lnbits/config.py b/lnbits/config.py
index 37b700fde..cf26ad21e 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -6,17 +6,19 @@ from typing import List, Optional
from pydantic import BaseSettings, Field
wallets_module = importlib.import_module("lnbits.wallets")
-wallet_class = getattr(
+wallet_class = getattr(
wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")
)
WALLET = wallet_class()
+
def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
- return v.replace(' ','').split(',')
+ return v.replace(" ", "").split(",")
+
class Settings(BaseSettings):
admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI")
@@ -24,7 +26,9 @@ class Settings(BaseSettings):
admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS")
- disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS")
+ disabled_ext: List[str] = Field(
+ default_factory=list, env="LNBITS_DISABLED_EXTENSIONS"
+ )
funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS")
# ops
data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER")
@@ -37,10 +41,17 @@ class Settings(BaseSettings):
denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
# Change theme
site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE")
- site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
+ site_tagline: str = Field(
+ default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE"
+ )
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
- default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
- theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
+ default_wallet_name: str = Field(
+ default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME"
+ )
+ theme: List[str] = Field(
+ default=["classic, flamingo, mint, salvador, monochrome, autumn"],
+ env="LNBITS_THEME_OPTIONS",
+ )
custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
@@ -48,7 +59,7 @@ class Settings(BaseSettings):
debug: Optional[str]
host: Optional[str]
port: Optional[str]
- lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
+ lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
# @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True)
# def validate(cls, val):
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 83648c444..3a1fbdfcf 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -39,6 +39,7 @@ from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
+
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index dd26d8feb..58b025aa3 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -235,7 +235,7 @@ async def check_user_exists(usr: UUID4) -> User:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
-
+
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index 7bd1b54a9..f4255c866 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -157,11 +157,13 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
url = f"{base}{endpoint}{url_params}"
return url
+
def removeEmptyString(arr):
return list(filter(None, arr))
+
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
- if(settings.LNBITS_ADMIN_UI):
+ if settings.LNBITS_ADMIN_UI:
_ = g().admin_conf
settings.LNBITS_AD_SPACE = _.ad_space
settings.LNBITS_HIDE_API = _.hide_api
@@ -170,8 +172,8 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
settings.LNBITS_SITE_TAGLINE = _.site_tagline
settings.LNBITS_SITE_DESCRIPTION = _.site_description
settings.LNBITS_THEME_OPTIONS = _.theme
- settings.LNBITS_CUSTOM_LOGO = _.custom_logo
-
+ settings.LNBITS_CUSTOM_LOGO = _.custom_logo
+
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
From 635bcf66d6f8cf7a65322a41abed92153039cd9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 11:47:24 +0200
Subject: [PATCH 082/134] format black
---
lnbits/app.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index f4e44a0f6..118bea983 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -56,6 +56,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
+
if lnbits.settings.LNBITS_ADMIN_UI:
g().admin_conf = conf
check_settings(app)
From 11393ef7e9ed808d9034baa8515eabcd37918a02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 15:29:12 +0200
Subject: [PATCH 083/134] fix AD_SPACE
---
lnbits/core/templates/core/wallet.html | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index 4bf6067c0..cc45eb681 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -385,12 +385,9 @@
- {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD =
- ADS.split(';') %}
+ {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %}
-
{% endfor %} {% endif %}
From 0beb112d5b4f938201adfc6fc2814717ddac20a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 15:55:37 +0200
Subject: [PATCH 084/134] fix some javascript errors when adding users
---
lnbits/extensions/admin/templates/admin/index.html | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d34b90683..1e881cb69 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1221,7 +1221,7 @@
addAdminUser() {
let addUser = this.data.admin_users_add
let admin_users = this.data.admin.admin_users
- if (addUser.length && !admin_users.includes(addUser)) {
+ if (addUser && addUser.length && !admin_users.includes(addUser)) {
admin_users.push(addUser)
this.data.admin.admin_users = admin_users
this.data.admin_users_add = ''
@@ -1234,7 +1234,7 @@
addAllowedUser() {
let addUser = this.data.allowed_users_add
let allowed_users = this.data.admin.allowed_users
- if (addUser.length && !allowed_users.includes(addUser)) {
+ if (addUser && addUser.length && !allowed_users.includes(addUser)) {
allowed_users.push(addUser)
this.data.admin.allowed_users = allowed_users
this.data.allowed_users_add = ''
@@ -1336,7 +1336,6 @@
custom_logo,
ad_space
} = this.data.admin
- //console.log("this", this.data.admin)
let data = {
admin_users: admin_users.toString(),
allowed_users: allowed_users.toString(),
From bfff5f3775cd090b454869c434042b4acdb37434 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 16:04:55 +0200
Subject: [PATCH 085/134] fix ADMIN_UI=false errors
---
lnbits/core/views/generic.py | 3 +++
lnbits/decorators.py | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 3a1fbdfcf..db4fac430 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -124,6 +124,9 @@ async def wallet(
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+ else:
+ LNBITS_ADMIN_USERS = []
+ LNBITS_ALLOWED_USERS = []
if not user_id:
user = await get_user((await create_account()).id)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 58b025aa3..5a3c0a5c3 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -141,6 +141,8 @@ async def get_key_type(
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ else:
+ LNBITS_ADMIN_USERS = []
for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
@@ -239,6 +241,10 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+ else:
+ LNBITS_ADMIN_USERS = []
+ LNBITS_ALLOWED_USERS = []
+
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
From cede3317f3655c09b196c5293174a1dbaaaa18ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 16:14:17 +0200
Subject: [PATCH 086/134] prettier
---
lnbits/core/templates/core/wallet.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index cc45eb681..5393007d1 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -386,8 +386,7 @@
{% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %}
-
-
{% endfor %} {% endif %}
From b442acc24f02c91971d29fbfe03dda9d5b5fc34c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 18:35:47 +0200
Subject: [PATCH 087/134] fix migration tests
---
lnbits/extensions/admin/migrations.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 388f5ec64..196c9fc07 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -7,7 +7,7 @@ from lnbits.helpers import urlsafe_short_hash
async def get_admin_user():
- if conf.admin_users[0]:
+ if len(conf.admin_users) > 0:
return conf.admin_users[0]
from lnbits.core.crud import create_account, get_user
From 6db5fb16c85d3fd654afd55ee8d6969d243572bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 26 Sep 2022 16:54:19 +0200
Subject: [PATCH 088/134] change comments to use multiple lines
---
.env.example | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/.env.example b/.env.example
index f0e21aa86..673470b7f 100644
--- a/.env.example
+++ b/.env.example
@@ -3,11 +3,15 @@ PORT=5000
DEBUG=false
-LNBITS_ADMIN_USERS="" # User IDs seperated by comma
-LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
+# User IDs seperated by comma
+LNBITS_ADMIN_USERS=""
+# Extensions only admin can access
+LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
+# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
+LNBITS_ADMIN_UI=false
-LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
+# Restricts access, User IDs seperated by comma
+LNBITS_ALLOWED_USERS=""
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
From affec50a3da3ac812b5046c49483676e93962b2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 26 Sep 2022 16:59:30 +0200
Subject: [PATCH 089/134] fix merge error
---
lnbits/app.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 118bea983..82a8f20e3 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -57,10 +57,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
- if lnbits.settings.LNBITS_ADMIN_UI:
- g().admin_conf = conf
- check_settings(app)
-
g().WALLET = WALLET
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
From 7aba2f989cdd78539ca22c69dc1a2e7d5ceb3d37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:14:36 +0200
Subject: [PATCH 090/134] use logger in app.py
---
lnbits/app.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 82a8f20e3..e07d2ada0 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -31,8 +31,6 @@ from .helpers import (
url_for_vendored,
)
from .requestvars import g
-
-# from .settings import WALLET
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
@@ -119,7 +117,7 @@ def check_settings(app: FastAPI):
admin_set = await get_admin_settings()
if admin_set:
break
- print("Waiting for admin settings... retrying in 5 seconds!")
+ logger.info("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(","))
@@ -129,7 +127,7 @@ def check_settings(app: FastAPI):
admin_set.theme = removeEmptyString(admin_set.theme.split(","))
admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(","))
g().admin_conf = conf.copy(update=admin_set.dict())
- print(
+ logger.info(
f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}"
)
From 212b8f9fdd67c73b16f2c0d97cd549bc7bb8f29e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:14:57 +0200
Subject: [PATCH 091/134] fix config
---
lnbits/config.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index cf26ad21e..874effae2 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -17,7 +17,11 @@ def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
- return v.replace(" ", "").split(",")
+ replaced = v.replace(" ", "")
+ if replaced:
+ return replaced.split(",")
+ else:
+ return []
class Settings(BaseSettings):
@@ -48,10 +52,7 @@ class Settings(BaseSettings):
default_wallet_name: str = Field(
default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME"
)
- theme: List[str] = Field(
- default=["classic, flamingo, mint, salvador, monochrome, autumn"],
- env="LNBITS_THEME_OPTIONS",
- )
+ theme: List[str] = Field(default_factory=list, env="LNBITS_THEME_OPTIONS")
custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
@@ -74,4 +75,5 @@ class Settings(BaseSettings):
conf = Settings()
+print(conf)
WALLET = wallet_class()
From b6a0a321844ff8a185f728b42f4ede3142334637 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:17:20 +0200
Subject: [PATCH 092/134] concatenate first migrations script
fixup
---
lnbits/config.py | 1 -
lnbits/extensions/admin/migrations.py | 7 +++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 874effae2..fe8dabf9c 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -75,5 +75,4 @@ class Settings(BaseSettings):
conf = Settings()
-print(conf)
WALLET = wallet_class()
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 196c9fc07..2d48a8e4c 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -23,6 +23,8 @@ async def get_admin_user():
async def m001_create_admin_table(db):
+
+
# users/server
user = await get_admin_user()
admin_users = ",".join(conf.admin_users)
@@ -78,7 +80,7 @@ async def m001_create_admin_table(db):
await db.execute(
"""
INSERT INTO admin.admin (
- "user",
+ "user",
admin_users,
allowed_users,
admin_ext,
@@ -126,9 +128,6 @@ async def m001_create_admin_table(db):
),
)
-
-async def m001_create_funding_table(db):
-
funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
# Make the funding table, if it does not already exist
From 1eeb9de7de8c2409f9a1d6b828e051ea640870d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 16:37:08 +0200
Subject: [PATCH 093/134] add restart button to frontend
---
.../admin/templates/admin/index.html | 28 ++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 1e881cb69..319ca3f0d 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -65,6 +65,14 @@
:options="data.funding_source"
>
+
+
+
Fee reserve
@@ -89,7 +97,7 @@
>
-
+
@@ -1257,6 +1265,24 @@
let spaces = this.data.admin.ad_space
this.data.admin.ad_space = spaces.filter(s => s !== ad)
},
+ restartServer() {
+ LNbits.api
+ .request(
+ 'GET',
+ '/admin/api/v1/admin/restart/',
+ this.g.user.wallets[0].adminkey
+ )
+ .then(response => {
+ this.$q.notify({
+ type: 'positive',
+ message: 'Success! Restarted Server',
+ icon: null
+ })
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
topupWallet() {
LNbits.api
.request(
From 82e322ae43658bef7735974324fd3bec7919b756 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:34:52 +0200
Subject: [PATCH 094/134] make extension use new settings
---
lnbits/extensions/boltz/boltz.py | 14 ++++++--------
lnbits/extensions/boltz/mempool.py | 15 ++++++---------
lnbits/extensions/boltz/views_api.py | 4 ++--
lnbits/extensions/lndhub/views_api.py | 4 ++--
lnbits/extensions/tpos/views.py | 14 +++++++-------
5 files changed, 23 insertions(+), 28 deletions(-)
diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py
index ac99d4f4a..424d0bf78 100644
--- a/lnbits/extensions/boltz/boltz.py
+++ b/lnbits/extensions/boltz/boltz.py
@@ -12,7 +12,7 @@ from loguru import logger
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL
+from lnbits.settings import settings
from .crud import update_swap_status
from .mempool import (
@@ -33,9 +33,7 @@ from .models import (
)
from .utils import check_balance, get_timestamp, req_wrap
-net = NETWORKS[BOLTZ_NETWORK]
-logger.trace(f"BOLTZ_URL: {BOLTZ_URL}")
-logger.trace(f"Bitcoin Network: {net['name']}")
+net = NETWORKS[settings.boltz_network]
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
@@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
res = req_wrap(
"post",
- f"{BOLTZ_URL}/createswap",
+ f"{settings.boltz_url}/createswap",
json={
"type": "submarine",
"pairId": "BTC/BTC",
@@ -129,7 +127,7 @@ async def create_reverse_swap(
res = req_wrap(
"post",
- f"{BOLTZ_URL}/createswap",
+ f"{settings.boltz_url}/createswap",
json={
"type": "reversesubmarine",
"pairId": "BTC/BTC",
@@ -409,7 +407,7 @@ def check_boltz_limits(amount):
def get_boltz_pairs():
res = req_wrap(
"get",
- f"{BOLTZ_URL}/getpairs",
+ f"{settings.boltz_url}/getpairs",
headers={"Content-Type": "application/json"},
)
return res.json()
@@ -418,7 +416,7 @@ def get_boltz_pairs():
def get_boltz_status(boltzid):
res = req_wrap(
"post",
- f"{BOLTZ_URL}/swapstatus",
+ f"{settings.boltz_url}/swapstatus",
json={"id": boltzid},
)
return res.json()
diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py
index a44c0f02c..a64cadad0 100644
--- a/lnbits/extensions/boltz/mempool.py
+++ b/lnbits/extensions/boltz/mempool.py
@@ -7,14 +7,11 @@ import websockets
from embit.transaction import Transaction
from loguru import logger
-from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS
+from lnbits.settings import settings
from .utils import req_wrap
-logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}")
-logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}")
-
-websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws"
+websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws"
async def wait_for_websocket_message(send, message_string):
@@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string):
def get_mempool_tx(address):
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs",
+ f"{settings.boltz_mempool_space_url}/api/address/{address}/txs",
headers={"Content-Type": "text/plain"},
)
txs = res.json()
@@ -70,7 +67,7 @@ def get_fee_estimation() -> int:
def get_mempool_fees() -> int:
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended",
+ f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended",
headers={"Content-Type": "text/plain"},
)
fees = res.json()
@@ -80,7 +77,7 @@ def get_mempool_fees() -> int:
def get_mempool_blockheight() -> int:
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height",
+ f"{settings.boltz_mempool_space_url}/api/blocks/tip/height",
headers={"Content-Type": "text/plain"},
)
return int(res.text)
@@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction):
logger.debug(f"Boltz - mempool sending onchain tx...")
req_wrap(
"post",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx",
+ f"{settings.boltz_mempool_space_url}/api/tx",
headers={"Content-Type": "text/plain"},
content=raw,
)
diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py
index a4b7d3181..18ca14cb7 100644
--- a/lnbits/extensions/boltz/views_api.py
+++ b/lnbits/extensions/boltz/views_api.py
@@ -14,7 +14,7 @@ from starlette.requests import Request
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL
+from lnbits.settings import settings
from . import boltz_ext
from .boltz import (
@@ -55,7 +55,7 @@ from .utils import check_balance
response_model=str,
)
async def api_mempool_url():
- return BOLTZ_MEMPOOL_SPACE_URL
+ return settings.boltz_mempool_space_url
# NORMAL SWAP
diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py
index 8cbe5a6bf..b2328c392 100644
--- a/lnbits/extensions/lndhub/views_api.py
+++ b/lnbits/extensions/lndhub/views_api.py
@@ -12,7 +12,7 @@ from lnbits import bolt11
from lnbits.core.crud import delete_expired_invoices, get_payments
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.decorators import WalletTypeInfo
-from lnbits.settings import LNBITS_SITE_TITLE, WALLET
+from lnbits.settings import WALLET, settings
from . import lndhub_ext
from .decorators import check_wallet, require_admin_key
@@ -56,7 +56,7 @@ async def lndhub_addinvoice(
_, pr = await create_invoice(
wallet_id=wallet.wallet.id,
amount=int(data.amt),
- memo=data.memo or LNBITS_SITE_TITLE,
+ memo=data.memo or settings.lnbits_site_title,
extra={"tag": "lndhub"},
)
except:
diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py
index e1f1d21e3..dac129a93 100644
--- a/lnbits/extensions/tpos/views.py
+++ b/lnbits/extensions/tpos/views.py
@@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
-from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE
+from lnbits.settings import settings
from . import tpos_ext, tpos_renderer
from .crud import get_tpos
@@ -50,12 +50,12 @@ async def manifest(tpos_id: str):
)
return {
- "short_name": LNBITS_SITE_TITLE,
- "name": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "short_name": settings.lnbits_site_title,
+ "name": tpos.name + " - " + settings.lnbits_site_title,
"icons": [
{
- "src": LNBITS_CUSTOM_LOGO
- if LNBITS_CUSTOM_LOGO
+ "src": settings.lnbits_custom_logo
+ if settings.lnbits_custom_logo
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
"type": "image/png",
"sizes": "900x900",
@@ -69,9 +69,9 @@ async def manifest(tpos_id: str):
"theme_color": "#1F2234",
"shortcuts": [
{
- "name": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "name": tpos.name + " - " + settings.lnbits_site_title,
"short_name": tpos.name,
- "description": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "description": tpos.name + " - " + settings.lnbits_site_title,
"url": "/tpos/" + tpos_id,
}
],
From 5aa9cdd45631379341f31d17d9484c5e3ad05a4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:35:26 +0200
Subject: [PATCH 095/134] remove enviroms
---
pyproject.toml | 1 -
1 file changed, 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 7418de272..92c43dce7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,6 @@ charset-normalizer = "2.0.6"
click = "8.0.1"
ecdsa = "0.17.0"
embit = "0.4.9"
-environs = "9.3.3"
fastapi = "0.78.0"
h11 = "0.12.0"
httpcore = "0.15.0"
From 6a2c7414783a5edb4b7932355f3fe145053332a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:36:14 +0200
Subject: [PATCH 096/134] fix admin
---
lnbits/extensions/admin/crud.py | 24 +-
lnbits/extensions/admin/migrations.py | 337 +----
lnbits/extensions/admin/models.py | 58 +-
.../admin/templates/admin/_tab_funding.html | 158 +++
.../admin/templates/admin/_tab_server.html | 78 ++
.../admin/templates/admin/_tab_theme.html | 122 ++
.../admin/templates/admin/_tab_users.html | 96 ++
.../admin/templates/admin/index.html | 1133 +----------------
lnbits/extensions/admin/views.py | 16 +-
lnbits/extensions/admin/views_api.py | 28 +-
10 files changed, 576 insertions(+), 1474 deletions(-)
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_funding.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_server.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_theme.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_users.html
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 0d7019ccf..e4cb5d77e 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -2,10 +2,11 @@ from typing import List
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
+from lnbits.settings import Settings
from lnbits.tasks import internal_invoice_queue
from . import db
-from .models import Admin, Funding
+from .models import Funding
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -23,26 +24,26 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
)
# manually send this for now
await internal_invoice_queue.put(internal_id)
- return payment
-async def update_admin(user: str, **kwargs) -> Admin:
+async def update_settings(user: str, **kwargs) -> Settings:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
await db.execute(
- f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
+ f'UPDATE admin.settings SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,))
+ row = await db.fetchone('SELECT * FROM admin.settings WHERE "user" = ?', (user,))
assert row, "Newly updated settings couldn't be retrieved"
- return Admin(**row) if row else None
-
-
-async def get_admin() -> Admin:
- row = await db.fetchone("SELECT * FROM admin.admin")
- return Admin(**row) if row else None
+ return Settings(**row) if row else None
async def update_funding(data: Funding) -> Funding:
+ await db.execute(
+ """
+ UPDATE admin.settings SET funding_source = ? WHERE user = ?
+ """,
+ (data.backend_wallet, data.user),
+ )
await db.execute(
"""
UPDATE admin.funding
@@ -69,5 +70,4 @@ async def update_funding(data: Funding) -> Funding:
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM admin.funding")
-
return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 2d48a8e4c..8f6c76a0e 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -1,292 +1,57 @@
-from os import getenv
-
-from sqlalchemy.exc import OperationalError # type: ignore
-
-from lnbits.config import conf
-from lnbits.helpers import urlsafe_short_hash
-
-
-async def get_admin_user():
- if len(conf.admin_users) > 0:
- return conf.admin_users[0]
- from lnbits.core.crud import create_account, get_user
-
- print("Seems like there's no admin users yet. Let's create an account for you!")
- account = await create_account()
- user = account.id
- assert user, "Newly created user couldn't be retrieved"
- print(
- f"Your newly created account/user id is: {user}. This will be the Super Admin user."
- )
- conf.admin_users.insert(0, user)
- return user
-
-
-async def m001_create_admin_table(db):
-
-
- # users/server
- user = await get_admin_user()
- admin_users = ",".join(conf.admin_users)
- allowed_users = ",".join(conf.allowed_users)
- admin_ext = ",".join(conf.admin_ext)
- disabled_ext = ",".join(conf.disabled_ext)
- funding_source = conf.funding_source
- # operational
- data_folder = conf.data_folder
- database_url = conf.database_url
- force_https = conf.force_https
- reserve_fee_min = conf.reserve_fee_min
- reserve_fee_pct = conf.reserve_fee_pct
- service_fee = conf.service_fee
- hide_api = conf.hide_api
- denomination = conf.denomination
- # Theme'ing
- site_title = conf.site_title
- site_tagline = conf.site_tagline
- site_description = conf.site_description
- default_wallet_name = conf.default_wallet_name
- theme = ",".join(conf.theme)
- custom_logo = conf.custom_logo
- ad_space = ",".join(conf.ad_space)
-
+async def m001_create_admin_settings_table(db):
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS admin.admin (
- "user" TEXT PRIMARY KEY,
- admin_users TEXT,
- allowed_users TEXT,
- admin_ext TEXT,
- disabled_ext TEXT,
- funding_source TEXT,
- data_folder TEXT,
- database_url TEXT,
- force_https BOOLEAN,
- reserve_fee_min INT,
- reserve_fee_pct REAL,
- service_fee REAL,
- hide_api BOOLEAN,
- denomination TEXT,
- site_title TEXT,
- site_tagline TEXT,
- site_description TEXT,
- default_wallet_name TEXT,
- theme TEXT,
- custom_logo TEXT,
- ad_space TEXT
- );
- """
- )
- await db.execute(
- """
- INSERT INTO admin.admin (
- "user",
- admin_users,
- allowed_users,
- admin_ext,
- disabled_ext,
- funding_source,
- data_folder,
- database_url,
- force_https,
- reserve_fee_min,
- reserve_fee_pct,
- service_fee,
- hide_api,
- denomination,
- site_title,
- site_tagline,
- site_description,
- default_wallet_name,
- theme,
- custom_logo,
- ad_space)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- user,
- admin_users,
- allowed_users,
- admin_ext,
- disabled_ext,
- funding_source,
- data_folder,
- database_url,
- force_https,
- reserve_fee_min,
- reserve_fee_pct,
- service_fee,
- hide_api,
- denomination,
- site_title,
- site_tagline,
- site_description,
- default_wallet_name,
- theme,
- custom_logo,
- ad_space,
- ),
- )
-
- funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
-
- # Make the funding table, if it does not already exist
- await db.execute(
- """
- CREATE TABLE IF NOT EXISTS admin.funding (
- id TEXT PRIMARY KEY,
- backend_wallet TEXT,
- endpoint TEXT,
+ CREATE TABLE IF NOT EXISTS admin.settings (
+ lnbits_admin_ui TEXT,
+ debug TEXT,
+ host TEXT,
port INT,
- read_key TEXT,
- invoice_key TEXT,
- admin_key TEXT,
- cert TEXT,
- balance INT,
- selected INT
+ lnbits_path TEXT,
+ lnbits_commit TEXT,
+ lnbits_admin_users TEXT,
+ lnbits_allowed_users TEXT,
+ lnbits_allowed_funding_sources TEXT,
+ lnbits_admin_extensions TEXT,
+ lnbits_disabled_extensions TEXT,
+ lnbits_site_title TEXT,
+ lnbits_site_tagline TEXT,
+ lnbits_site_description TEXT,
+ lnbits_default_wallet_name TEXT,
+ lnbits_theme_options TEXT,
+ lnbits_custom_logo TEXT,
+ lnbits_ad_space TEXT,
+ lnbits_data_folder TEXT,
+ lnbits_database_url TEXT,
+ lnbits_force_https TEXT,
+ lnbits_reserve_fee_min TEXT,
+ lnbits_reserve_fee_percent TEXT,
+ lnbits_service_fee TEXT,
+ lnbits_hide_api TEXT,
+ lnbits_denomination TEXT,
+ lnbits_backend_wallet_class TEXT,
+ fake_wallet_secret TEXT,
+ lnbits_endpoint TEXT,
+ lnbits_key TEXT,
+ cliche_endpoint TEXT,
+ corelightning_rpc TEXT,
+ eclair_url TEXT,
+ eclair_pass TEXT,
+ lnd_rest_endpoint TEXT,
+ lnd_rest_cert TEXT,
+ lnd_rest_macaroon TEXT,
+ lnpay_api_endpoint TEXT,
+ lnpay_api_key TEXT,
+ lnpay_wallet_key TEXT,
+ lntxbot_api_endpoint TEXT,
+ lntxbot_key TEXT,
+ opennode_api_endpoint TEXT,
+ opennode_key TEXT,
+ spark_url TEXT,
+ spark_token TEXT,
+ boltz_network TEXT,
+ boltz_url TEXT,
+ boltz_mempool_space_url TEXT,
+ boltz_mempool_space_url_ws TEXT
);
"""
)
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, selected)
- VALUES (?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "CLightningWallet",
- getenv("CLIGHTNING_RPC"),
- 1 if funding_wallet == "CLightningWallet" else 0,
- ),
- )
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "SparkWallet",
- getenv("SPARK_URL"),
- getenv("SPARK_TOKEN"),
- 1 if funding_wallet == "SparkWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LnbitsWallet",
- getenv("LNBITS_ENDPOINT"),
- getenv("LNBITS_KEY"),
- 1 if funding_wallet == "LnbitsWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LndWallet",
- getenv("LND_GRPC_ENDPOINT"),
- getenv("LND_GRPC_PORT"),
- getenv("LND_GRPC_MACAROON"),
- getenv("LND_GRPC_CERT"),
- 1 if funding_wallet == "LndWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LndRestWallet",
- getenv("LND_REST_ENDPOINT"),
- getenv("LND_REST_MACAROON"),
- getenv("LND_REST_CERT"),
- 1 if funding_wallet == "LndWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LNPayWallet",
- getenv("LNPAY_API_ENDPOINT"),
- getenv("LNPAY_WALLET_KEY"),
- getenv("LNPAY_API_KEY"), # this is going in as the cert
- 1 if funding_wallet == "LNPayWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LntxbotWallet",
- getenv("LNTXBOT_API_ENDPOINT"),
- getenv("LNTXBOT_KEY"),
- 1 if funding_wallet == "LntxbotWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "OpenNodeWallet",
- getenv("OPENNODE_API_ENDPOINT"),
- getenv("OPENNODE_KEY"),
- 1 if funding_wallet == "OpenNodeWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "SparkWallet",
- getenv("SPARK_URL"),
- getenv("SPARK_TOKEN"),
- 1 if funding_wallet == "SparkWallet" else 0,
- ),
- )
-
- ## PLACEHOLDER FOR ECLAIR WALLET
- # await db.execute(
- # """
- # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- # VALUES (?, ?, ?, ?, ?)
- # """,
- # (
- # urlsafe_short_hash(),
- # "EclairWallet",
- # getenv("ECLAIR_URL"),
- # getenv("ECLAIR_PASS"),
- # 1 if funding_wallet == "EclairWallet" else 0,
- # ),
- # )
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 6e95d68fb..ef57cadd3 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -29,36 +29,36 @@ class UpdateAdminSettings(BaseModel):
ad_space: str = Query(None)
-class Admin(BaseModel):
- # users
- user: str
- admin_users: Optional[str]
- allowed_users: Optional[str]
- admin_ext: Optional[str]
- disabled_ext: Optional[str]
- funding_source: Optional[str]
- # ops
- data_folder: Optional[str]
- database_url: Optional[str]
- force_https: bool = Field(default=True)
- reserve_fee_min: Optional[int]
- reserve_fee_pct: Optional[float]
- service_fee: float = Optional[float]
- hide_api: bool = Field(default=False)
- # Change theme
- site_title: Optional[str]
- site_tagline: Optional[str]
- site_description: Optional[str]
- default_wallet_name: Optional[str]
- denomination: str = Field(default="sats")
- theme: Optional[str]
- custom_logo: Optional[str]
- ad_space: Optional[str]
+# class Admin(BaseModel):
+# # users
+# user: str
+# admin_users: Optional[str]
+# allowed_users: Optional[str]
+# admin_ext: Optional[str]
+# disabled_ext: Optional[str]
+# funding_source: Optional[str]
+# # ops
+# data_folder: Optional[str]
+# database_url: Optional[str]
+# force_https: bool = Field(default=True)
+# reserve_fee_min: Optional[int]
+# reserve_fee_pct: Optional[float]
+# service_fee: float = Optional[float]
+# hide_api: bool = Field(default=False)
+# # Change theme
+# site_title: Optional[str]
+# site_tagline: Optional[str]
+# site_description: Optional[str]
+# default_wallet_name: Optional[str]
+# denomination: str = Field(default="sats")
+# theme: Optional[str]
+# custom_logo: Optional[str]
+# ad_space: Optional[str]
- @classmethod
- def from_row(cls, row: Row) -> "Admin":
- data = dict(row)
- return cls(**data)
+# @classmethod
+# def from_row(cls, row: Row) -> "Admin":
+# data = dict(row)
+# return cls(**data)
class Funding(BaseModel):
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
new file mode 100644
index 000000000..2ed0aae27
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -0,0 +1,158 @@
+
+
+ Wallets Management
+
+
+
+
+
Funding Source Info
+
+ {%raw%}
+ -
+ Funding Source: {{data.settings.lnbits_backend_wallet_class}}
+
+ - Balance: {{data.balance / 1000}} sats
+ {%endraw%}
+
+
+
+
+
+
+
+
+
Active Funding (Requires server restart)
+
+
+
+
+
+
+
+
+
+
+
TopUp a wallet
+
+
+
+
+
+
+
+
Funding Sources
+ {% raw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_server.html b/lnbits/extensions/admin/templates/admin/_tab_server.html
new file mode 100644
index 000000000..2924e6a40
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_server.html
@@ -0,0 +1,78 @@
+
+
+ Server Management
+
+
+
+
+
Server Info
+
+ {%raw%}
+ -
+ SQlite: {{data.settings.lnbits_data_folder}}
+
+ -
+ Postgres: {{data.settings.lnbits_database_url}}
+
+ {%endraw%}
+
+
+
+
+
+
+
+
Miscelaneous
+
+
+ Force HTTPS
+ Prefer secure URLs
+
+
+
+
+
+
+
+ Hide API
+ Hides wallet api, extensions can choose to honor
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html
new file mode 100644
index 000000000..41dc04477
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html
@@ -0,0 +1,122 @@
+
+
+ UI Management
+
+
+
+
+
+
+
+
Default Wallet Name
+
+
+
+
+
+
+
+
+
Advertisement Slots
+
+
+
+
+ {% raw %}
+
+ {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
new file mode 100644
index 000000000..3eb53ac48
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -0,0 +1,96 @@
+
+
+ User Management
+
+
+ Super Admin: {% raw %}{{this.data.settings.lnbits_admin_users[0]}}{%
+ endraw %}
+
+
+
+
Admin Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
Allowed Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 319ca3f0d..87e893215 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -29,1118 +29,16 @@
-
-
-
- Wallets Management
-
-
-
-
-
Funding Source Info
-
- {%raw%}
- - Funding Source: {{data.admin.funding_source}}
- - Balance: {{data.admin.balance / 1000}} sats
- {%endraw%}
-
-
-
-
-
-
-
-
-
- Active Funding
- (Requires server restart)
-
-
-
-
-
-
-
-
-
-
-
-
-
TopUp a wallet
-
-
-
-
-
-
-
-
-
Funding Sources
- {% raw %}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% endraw %}
-
-
-
-
-
-
- User Management
-
-
- Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %}
-
-
-
-
Admin Users
-
-
-
-
- {% raw %}
-
- {{ user }}
-
- {% endraw %}
-
-
-
-
-
Allowed Users
-
-
-
-
- {% raw %}
-
- {{ user }}
-
- {% endraw %}
-
-
-
-
-
-
-
Disabled Extensions
-
-
-
-
-
- Save
-
-
-
-
-
- Server Management
-
-
-
-
-
Server Info
-
- {%raw%}
- -
- SQlite: {{data.admin.data_folder}}
-
- -
- Postgres: {{data.admin.database_url}}
-
- {%endraw%}
-
-
-
-
-
-
-
-
Miscelaneous
-
-
- Force HTTPS
- Prefer secure URLs
-
-
-
-
-
-
-
- Hide API
- Hides wallet api, extensions can choose to
- honor
-
-
-
-
-
-
-
-
-
-
-
- Save
-
-
-
-
-
- UI Management
-
-
-
-
-
-
-
-
Default Wallet Name
-
-
-
-
-
-
-
-
-
Advertisement Slots
-
-
-
-
- {% raw %}
-
- {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
-
- {% endraw %}
-
-
-
-
-
-
-
-
- Save
-
-
-
+ {% include "admin/_tab_funding.html" %} {% include
+ "admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {%
+ include "admin/_tab_theme.html" %}
-
-
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }}
{% endblock %}
From 04b37458983094ff9deec4ba010ec2a93c002cd9 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 7 Oct 2022 19:24:07 +0100
Subject: [PATCH 122/134] make saving possible (possible will change in future)
---
.../admin/templates/admin/index.html | 36 ++++++++++++-------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 72352651b..18df16a97 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -121,8 +121,11 @@
created: function () {
this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data
this.balance = +'{{ balance|safe }}'
- this.formData = this.settings //model
+ this.formData = _.clone(this.settings) //model
+ //this.formData.lnbits_ad_space = "hdh"
console.log(this.formData)
+ console.log(_.isEqual(this.settings, this.formData))
+
},
methods: {
addAdminUser() {
@@ -206,18 +209,27 @@
},
updateSettings() {
let data = {
- ...this.settings,
- ...this.formData
+ lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class,
+ lnbits_admin_users: this.formData.lnbits_admin_users.toString(),
+ lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(),
+ lnbits_admin_ext: this.formData.lnbits_admin_ext,
+ lnbits_disabled_ext: this.formData.lnbits_disabled_ext,
+ lnbits_funding_source: this.formData.lnbits_funding_source,
+ lnbits_force_https: this.formData.lnbits_force_https,
+ lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min,
+ lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent,
+ lnbits_service_fee: this.formData.lnbits_service_fee,
+ lnbits_hide_api: this.formData.lnbits_hide_api,
+ lnbits_site_title: this.formData.lnbits_site_title,
+ lnbits_site_tagline: this.formData.lnbits_site_tagline,
+ lnbits_site_description: this.formData.lnbits_site_description,
+ lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name,
+ lnbits_denomination: this.formData.lnbits_denomination,
+ lnbits_theme: this.formData.lnbits_theme,
+ lnbits_custom_logo: this.formData.lnbits_custom_logo,
+ lnbits_ad_space: this.formData.lnbits_ad_space.toString()
}
- /*
- const formElement = document.getElementById('settings_form')
- const formData = new FormData(formElement)
- const data = {}
- formData.forEach((value, key) => (data[key] = value))
- // only for debugging
- for (const [key, value] of formData) {
- console.log(`${key}: ${value}\n`)
- }*/
+ console.log(data)
LNbits.api
.request(
'PUT',
From cc42df12f4d3927c854675f114aff0b75bef6d19 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 7 Oct 2022 19:44:03 +0100
Subject: [PATCH 123/134] some more refining
---
lnbits/extensions/admin/models.py | 10 +++++-----
.../extensions/admin/templates/admin/_tab_users.html | 2 ++
lnbits/extensions/admin/templates/admin/index.html | 12 +++++++++---
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 13a6cd233..45cd990d4 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -4,10 +4,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: str = Query(None)
- lnbits_allowed_users: str = Query(None)
- lnbits_admin_ext: str = Query(None)
- lnbits_disabled_ext: str = Query(None)
+ lnbits_admin_users: str = Query(None) #this should be List[str] ??
+ lnbits_allowed_users: str = Query(None) #this should be List[str] ??
+ lnbits_admin_ext: str = Query(None) #this should be List[str] ??
+ lnbits_disabled_ext: str = Query(None) #this should be List[str] ??
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -21,4 +21,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: str = Query(None)
+ lnbits_ad_space: str = Query(None) #this should be List[str] ??
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
index c396ba7d6..08b08a628 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_users.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -71,6 +71,7 @@
multiple
hint="Extensions only user with admin privileges can use"
label="Admin extensions"
+ :options="g.extensions.map(e => e.name)"
>
@@ -79,6 +80,7 @@
-
+
Date: Mon, 10 Oct 2022 12:17:35 +0100
Subject: [PATCH 124/134] get saved data and alert when data changed
---
.../admin/templates/admin/index.html | 18 +++++++-----------
lnbits/extensions/admin/views_api.py | 5 +++--
2 files changed, 10 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 4754656d7..4e401cb40 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,7 @@
-
+
{
+ this.settings = response.data.settings
+ this.formData = _.clone(this.settings)
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c81205643..c2079e377 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -43,8 +43,9 @@ async def api_update_settings(
user: User = Depends(check_admin),
data: UpdateSettings = Body(...),
):
- await update_settings(data)
- return {"status": "Success"}
+ settings = await update_settings(data)
+ logger.debug(settings)
+ return {"status": "Success", "settings": settings.dict()}
@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK)
From 001f6589bb3c173c9ada0f86ec0a2eca08f3b051 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 10 Oct 2022 12:23:19 +0100
Subject: [PATCH 125/134] cleanup and typing fix for data
---
lnbits/extensions/admin/models.py | 12 +++++-----
.../admin/templates/admin/index.html | 22 ++-----------------
lnbits/extensions/admin/views_api.py | 1 -
3 files changed, 9 insertions(+), 26 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 45cd990d4..94fa56bb7 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,13 +1,15 @@
+from typing import List
+
from fastapi import Query
from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: str = Query(None) #this should be List[str] ??
- lnbits_allowed_users: str = Query(None) #this should be List[str] ??
- lnbits_admin_ext: str = Query(None) #this should be List[str] ??
- lnbits_disabled_ext: str = Query(None) #this should be List[str] ??
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -21,4 +23,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: str = Query(None) #this should be List[str] ??
+ lnbits_ad_space: List[str] = Query(None)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 4e401cb40..d8111595f 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -209,26 +209,8 @@
})
},
updateSettings() {
- let data = {
- lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class,
- lnbits_admin_users: this.formData.lnbits_admin_users.toString(),
- lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(),
- lnbits_admin_ext: this.formData.lnbits_admin_ext,
- lnbits_disabled_ext: this.formData.lnbits_disabled_ext,
- lnbits_funding_source: this.formData.lnbits_funding_source,
- lnbits_force_https: this.formData.lnbits_force_https,
- lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min,
- lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent,
- lnbits_service_fee: this.formData.lnbits_service_fee,
- lnbits_hide_api: this.formData.lnbits_hide_api,
- lnbits_site_title: this.formData.lnbits_site_title,
- lnbits_site_tagline: this.formData.lnbits_site_tagline,
- lnbits_site_description: this.formData.lnbits_site_description,
- lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name,
- lnbits_denomination: this.formData.lnbits_denomination,
- lnbits_theme: this.formData.lnbits_theme,
- lnbits_custom_logo: this.formData.lnbits_custom_logo,
- lnbits_ad_space: this.formData.lnbits_ad_space.toString()
+ let data = {
+ ...this.formData
}
LNbits.api
.request(
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c2079e377..19b52e352 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -44,7 +44,6 @@ async def api_update_settings(
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
- logger.debug(settings)
return {"status": "Success", "settings": settings.dict()}
From 1cc54ff4b7e19366499743fc9cb881bb58ccf2c6 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 12 Oct 2022 19:04:46 +0100
Subject: [PATCH 126/134] add funding sources options
---
lnbits/app.py | 1 +
lnbits/extensions/admin/models.py | 41 +++-
.../admin/templates/admin/_tab_funding.html | 25 +-
.../admin/templates/admin/index.html | 217 +++++++++++++++++-
4 files changed, 259 insertions(+), 25 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index a8371950b..50f218b72 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -84,6 +84,7 @@ async def check_funding_source() -> None:
def signal_handler(signal, frame):
logger.debug(f"SIGINT received, terminating LNbits.")
sys.exit(1)
+
signal.signal(signal.SIGINT, signal_handler)
WALLET = get_wallet_class()
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 94fa56bb7..d9d2b22ff 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,10 +6,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: List[str] = Query(None)
- lnbits_allowed_users: List[str] = Query(None)
- lnbits_admin_ext: List[str] = Query(None)
- lnbits_disabled_ext: List[str] = Query(None)
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -23,4 +23,35 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: List[str] = Query(None)
+ lnbits_ad_space: List[str] = Query(None)
+
+ # funding sources
+ fake_wallet_secret: str = Query(None)
+ lnbits_endpoint: str = Query(None)
+ lnbits_key: str = Query(None)
+ cliche_endpoint: str = Query(None)
+ corelightning_rpc: str = Query(None)
+ eclair_url: str = Query(None)
+ eclair_pass: str = Query(None)
+ lnd_rest_endpoint: str = Query(None)
+ lnd_rest_cert: str = Query(None)
+ lnd_rest_macaroon: str = Query(None)
+ lnd_rest_macaroon_encrypted: str = Query(None)
+ lnd_cert: str = Query(None)
+ lnd_admin_macaroon: str = Query(None)
+ lnd_invoice_macaroon: str = Query(None)
+ lnd_grpc_endpoint: str = Query(None)
+ lnd_grpc_cert: str = Query(None)
+ lnd_grpc_port: int = Query(None, ge=0)
+ lnd_grpc_admin_macaroon: str = Query(None)
+ lnd_grpc_invoice_macaroon: str = Query(None)
+ lnd_grpc_macaroon_encrypted: str = Query(None)
+ lnpay_api_endpoint: str = Query(None)
+ lnpay_api_key: str = Query(None)
+ lnpay_wallet_key: str = Query(None)
+ lntxbot_api_endpoint: str = Query(None)
+ lntxbot_key: str = Query(None)
+ opennode_api_endpoint: str = Query(None)
+ opennode_key: str = Query(None)
+ spark_url: str = Query(None)
+ spark_token: str = Query(None)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index 8b5456f19..a523d4e59 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -8,9 +8,7 @@
Funding Source Info
{%raw%}
- -
- Funding Source: {{settings.lnbits_backend_wallet_class}}
-
+ - Funding Source: {{settings.lnbits_backend_wallet_class}}
- Balance: {{balance / 1000}} sats
{%endraw%}
@@ -60,21 +58,30 @@
- Funding Sources
+ Funding Sources (Requires server restart)
-
+
-
-
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d8111595f..ccaddda43 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,13 @@
-
+
u !== user
- )
+ this.settings.lnbits_admin_users = admin_users.filter(u => u !== user)
},
addAllowedUser() {
let addUser = this.formData.allowed_users_add
@@ -155,7 +335,9 @@
},
removeAllowedUser(user) {
let allowed_users = this.settings.lnbits_allowed_users
- this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user)
+ this.settings.lnbits_allowed_users = allowed_users.filter(
+ u => u !== user
+ )
},
addAdSpace() {
let adSpace = this.formData.ad_space_add
@@ -208,8 +390,19 @@
LNbits.utils.notifyApiError(error)
})
},
+ updateFundingData(){
+ this.settings.lnbits_allowed_funding_sources.map(f => {
+ let opts = this.funding_sources.get(f)
+ if (!opts) return
+
+ Object.keys(opts).forEach(e => {
+ opts[e].value = this.settings[e]
+ })
+ })
+ console.log("funding", this.funding_sources)
+ },
updateSettings() {
- let data = {
+ let data = {
...this.formData
}
LNbits.api
@@ -222,11 +415,13 @@
.then(response => {
this.settings = response.data.settings
this.formData = _.clone(this.settings)
+ this.updateFundingData()
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
icon: null
})
+ console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -262,7 +457,7 @@
LNbits.utils.notifyApiError(error)
})
}
- },
+ }
})
{% endblock %}
From 761fc427defc590913124f1ad2f1b518633fbad4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 10 Oct 2022 23:27:46 +0200
Subject: [PATCH 127/134] add callback for saas app
---
lnbits/app.py | 2 +-
lnbits/extensions/admin/migrations.py | 3 +++
lnbits/extensions/admin/views_api.py | 5 -----
lnbits/settings.py | 32 +++++++++++++++++++++++----
4 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 50f218b72..49ad8d77d 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -64,7 +64,7 @@ def create_app() -> FastAPI:
# TODO: why those 2?
g().config = settings
- # g().base_url = f"http://{settings.host}:{settings.port}"
+ g().base_url = f"http://{settings.host}:{settings.port}"
app.add_middleware(GZipMiddleware, minimum_size=1000)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index c4bc98d84..ea698c27b 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,6 +6,9 @@ async def m001_create_admin_settings_table(db):
debug TEXT,
host TEXT,
port INTEGER,
+ lnbits_saas_instance_id TEXT,
+ lnbits_saas_callback TEXT,
+ lnbits_saas_secret TEXT,
lnbits_path TEXT,
lnbits_commit TEXT,
lnbits_admin_users TEXT,
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 19b52e352..ae2959bc8 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -53,8 +53,3 @@ async def api_delete_settings(
):
await delete_settings()
return {"status": "Success"}
-
-
-@admin_ext.get("/api/v1/backup/", status_code=HTTPStatus.OK)
-async def api_backup(user: User = Depends(check_admin)):
- return {"status": "not implemented"}
diff --git a/lnbits/settings.py b/lnbits/settings.py
index ffcdcc0a9..f183211c4 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,6 +1,7 @@
import importlib
import json
import subprocess
+import httpx
from os import path
from sqlite3 import Row
from typing import List, Optional
@@ -9,6 +10,7 @@ from loguru import logger
from pydantic import BaseSettings, Field, validator
+
def list_parse_fallback(v):
try:
return json.loads(v)
@@ -34,6 +36,11 @@ class Settings(BaseSettings):
lnbits_path: str = Field(default=".")
lnbits_commit: str = Field(default="unknown")
+ # saas
+ lnbits_saas_callback: Optional[str] = Field(default=None)
+ lnbits_saas_secret: Optional[str] = Field(default=None)
+ lnbits_saas_instance_id: Optional[str] = Field(default=None)
+
# users
lnbits_admin_users: List[str] = Field(default=[])
lnbits_allowed_users: List[str] = Field(default=[])
@@ -230,11 +237,28 @@ async def check_admin_settings():
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
- logger.warning(
- f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}"
- )
+
+ admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ logger.warning(f"✔️ Access admin user account at: {admin_url}")
+
+ if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id:
+ with httpx.Client() as client:
+ headers = {
+ "Content-Type": "application/json; charset=utf-8",
+ "X-API-KEY": settings.lnbits_saas_secret
+ }
+ payload = {
+ "instance_id": settings.lnbits_saas_instance_id,
+ "adminuser": user
+ }
+ try:
+ r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload)
+ logger.warning("sent admin user to saas application")
+ except:
+ logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}")
+
except:
- logger.warning("admin.settings tables does not exist.")
+ logger.error("admin.settings tables does not exist.")
raise
From 620fd2569655aa0f45b486201bcac75654f28326 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Wed, 12 Oct 2022 13:08:59 +0200
Subject: [PATCH 128/134] bugfixes and fix topup wallet
---
.../admin/templates/admin/index.html | 6 ++--
lnbits/extensions/admin/views_api.py | 36 ++++++++++---------
lnbits/server.py | 1 +
lnbits/settings.py | 29 ++++++++++-----
lnbits/wallets/fake.py | 2 +-
5 files changed, 44 insertions(+), 30 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index ccaddda43..575b377f2 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -369,10 +369,10 @@
topupWallet() {
LNbits.api
.request(
- 'POST',
+ 'PUT',
'/admin/api/v1/topup/?usr=' + this.g.user.id,
- this.wallet.id,
- this.wallet.amount
+ this.g.user.wallets[0].adminkey,
+ this.wallet
)
.then(response => {
this.$q.notify({
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index ae2959bc8..63ed5b3ca 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,7 +1,6 @@
from http import HTTPStatus
-from fastapi import Body, Depends, Request
-from loguru import logger
+from fastapi import Body, Depends, Query
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
@@ -9,47 +8,50 @@ from lnbits.core.models import User
from lnbits.decorators import check_admin
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
-from lnbits.requestvars import g
from lnbits.server import server_restart
-from lnbits.settings import settings
from .crud import delete_settings, update_settings, update_wallet_balance
-@admin_ext.get("/api/v1/restart/", status_code=HTTPStatus.OK)
-async def api_restart_server(user: User = Depends(check_admin)):
+@admin_ext.get(
+ "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
+async def api_restart_server() -> dict[str, str]:
server_restart.set()
return {"status": "Success"}
-@admin_ext.put("/api/v1/topup/", status_code=HTTPStatus.OK)
+@admin_ext.put(
+ "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
async def api_update_balance(
- wallet_id, topup_amount: int, user: User = Depends(check_admin)
-):
+ id: str = Body(...), amount: int = Body(...)
+) -> dict[str, str]:
try:
- wallet = await get_wallet(wallet_id)
+ await get_wallet(id)
except:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
)
- await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
+ await update_wallet_balance(wallet_id=id, amount=int(amount))
return {"status": "Success"}
-@admin_ext.put("/api/v1/settings/", status_code=HTTPStatus.OK)
+@admin_ext.put(
+ "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
async def api_update_settings(
- user: User = Depends(check_admin),
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
return {"status": "Success", "settings": settings.dict()}
-@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK)
-async def api_delete_settings(
- user: User = Depends(check_admin),
-):
+@admin_ext.delete(
+ "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
+async def api_delete_settings() -> dict[str, str]:
await delete_settings()
return {"status": "Success"}
diff --git a/lnbits/server.py b/lnbits/server.py
index 79af8112f..6d4cd2e70 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -52,6 +52,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
port=port,
host=host,
reload=reload,
+ forwarded_allow_ips="*",
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d
diff --git a/lnbits/settings.py b/lnbits/settings.py
index f183211c4..61dbd6f29 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,20 +1,19 @@
import importlib
import json
import subprocess
-import httpx
from os import path
from sqlite3 import Row
from typing import List, Optional
+import httpx
from loguru import logger
from pydantic import BaseSettings, Field, validator
-
def list_parse_fallback(v):
try:
return json.loads(v)
- except Exception as e:
+ except Exception:
replaced = v.replace(" ", "")
if replaced:
return replaced.split(",")
@@ -238,24 +237,36 @@ async def check_admin_settings():
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
- admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ admin_url = (
+ f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ )
logger.warning(f"✔️ Access admin user account at: {admin_url}")
- if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id:
+ if (
+ settings.lnbits_saas_callback
+ and settings.lnbits_saas_secret
+ and settings.lnbits_saas_instance_id
+ ):
with httpx.Client() as client:
headers = {
"Content-Type": "application/json; charset=utf-8",
- "X-API-KEY": settings.lnbits_saas_secret
+ "X-API-KEY": settings.lnbits_saas_secret,
}
payload = {
"instance_id": settings.lnbits_saas_instance_id,
- "adminuser": user
+ "adminuser": user,
}
try:
- r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload)
+ client.post(
+ settings.lnbits_saas_callback,
+ headers=headers,
+ json=payload,
+ )
logger.warning("sent admin user to saas application")
except:
- logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}")
+ logger.error(
+ f"error sending admin user to saas: {settings.lnbits_saas_callback}"
+ )
except:
logger.error("admin.settings tables does not exist.")
diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py
index 73458e8c4..94ff5f48b 100644
--- a/lnbits/wallets/fake.py
+++ b/lnbits/wallets/fake.py
@@ -19,7 +19,6 @@ from .base import (
class FakeWallet(Wallet):
- queue: asyncio.Queue = asyncio.Queue(0)
secret: str = settings.fake_wallet_secret
privkey: str = hashlib.pbkdf2_hmac(
"sha256",
@@ -98,6 +97,7 @@ class FakeWallet(Wallet):
return PaymentStatus(None)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
+ self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value: Invoice = await self.queue.get()
yield value.payment_hash
From 9dbcd89c6ebdba4db6c0bc7d70d570a8ee5fffaa Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 13 Oct 2022 15:52:02 +0100
Subject: [PATCH 129/134] added tooltips, moved reset, and warnings
---
.../admin/templates/admin/index.html | 97 ++++++++++++-------
1 file changed, 61 insertions(+), 36 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 575b377f2..7d268301b 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1,8 +1,14 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
-
-
+
+
+ Save your changes
-
-
-
+
+
+ Restart the server for changes to take effect
+
+
+
+
+ Add funds to a wallet.
+
+ > -->
+
+ Delete all settings and reset to defaults.
+
@@ -121,6 +136,7 @@
show: false
},
tab: 'funding',
+ needsRestart: false,
funding_sources: new Map([
['VoidWallet', null],
[
@@ -302,13 +318,12 @@
this.balance = +'{{ balance|safe }}'
this.formData = _.clone(this.settings) //model
this.updateFundingData()
-
console.log(this.settings)
},
computed: {
checkChanges() {
return !_.isEqual(this.settings, this.formData)
- },
+ }
},
methods: {
addAdminUser() {
@@ -361,6 +376,7 @@
message: 'Success! Restarted Server',
icon: null
})
+ this.needsRestart = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -390,16 +406,15 @@
LNbits.utils.notifyApiError(error)
})
},
- updateFundingData(){
+ updateFundingData() {
this.settings.lnbits_allowed_funding_sources.map(f => {
let opts = this.funding_sources.get(f)
if (!opts) return
-
+
Object.keys(opts).forEach(e => {
opts[e].value = this.settings[e]
})
})
- console.log("funding", this.funding_sources)
},
updateSettings() {
let data = {
@@ -415,31 +430,41 @@
.then(response => {
this.settings = response.data.settings
this.formData = _.clone(this.settings)
+ this.needsRestart = true
this.updateFundingData()
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
icon: null
})
- console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteSettings() {
- LNbits.api
- .request('DELETE', '/admin/api/v1/settings/?usr=' + this.g.user.id)
- .then(response => {
- this.$q.notify({
- type: 'positive',
- message:
- 'Success! Restored settings to defaults, restart required!',
- icon: null
- })
- })
- .catch(function (error) {
- LNbits.utils.notifyApiError(error)
+ LNbits.utils
+ .confirmDialog(
+ 'Are you sure you want to restore settings to default?'
+ )
+ .onOk(() => {
+ LNbits.api
+ .request(
+ 'DELETE',
+ '/admin/api/v1/settings/?usr=' + this.g.user.id
+ )
+ .then(response => {
+ this.$q.notify({
+ type: 'positive',
+ message:
+ 'Success! Restored settings to defaults, restart required!',
+ icon: null
+ })
+ this.needsRestart = true
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
})
},
downloadBackup() {
From 31a7a87774e5fcd87962440bc8c20510f607a328 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 13 Oct 2022 15:52:26 +0100
Subject: [PATCH 130/134] moved to columns
---
.../admin/templates/admin/_tab_funding.html | 54 +++++++++----------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index a523d4e59..a69ecb47e 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -27,38 +27,38 @@
:options="settings.lnbits_allowed_funding_sources"
>
-
+
-
-
-
- Funding Sources (Requires server restart)
+
+ Funding Sources (Requires server restart)
+
Date: Thu, 13 Oct 2022 15:52:39 +0100
Subject: [PATCH 131/134] themes not displaying fixed
---
lnbits/extensions/admin/templates/admin/_tab_theme.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html
index 46bf83e9c..c327733ff 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_theme.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html
@@ -63,7 +63,7 @@
Themes
Date: Fri, 21 Oct 2022 10:00:47 +0200
Subject: [PATCH 132/134] add loop to uvicorn
---
lnbits/server.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lnbits/server.py b/lnbits/server.py
index 6d4cd2e70..eb7c12b16 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,12 +1,7 @@
-import asyncio
-
import uvloop
-
uvloop.install()
-import contextlib
import multiprocessing as mp
-import sys
import time
import click
@@ -49,6 +44,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
while True:
config = uvicorn.Config(
"lnbits.__main__:app",
+ loop="uvloop",
port=port,
host=host,
reload=reload,
@@ -65,9 +61,10 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
server_restart.clear()
server.should_exit = True
server.force_exit = True
+ time.sleep(3)
process.terminate()
process.join()
- time.sleep(3)
+ time.sleep(1)
server_restart = mp.Event()
From b14b9f3b3a48f5a2544864783de7129e8ae5bfb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 11:13:40 +0200
Subject: [PATCH 133/134] add get settings endpoint with only values you can
also save
---
lnbits/app.py | 6 +----
lnbits/extensions/admin/crud.py | 12 +++++++++-
lnbits/extensions/admin/models.py | 6 ++++-
.../admin/templates/admin/index.html | 22 +++++++++++++++----
lnbits/extensions/admin/views_api.py | 7 +++++-
lnbits/server.py | 1 +
6 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 49ad8d77d..959a81689 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -62,10 +62,6 @@ def create_app() -> FastAPI:
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
- # TODO: why those 2?
- g().config = settings
- g().base_url = f"http://{settings.host}:{settings.port}"
-
app.add_middleware(GZipMiddleware, minimum_size=1000)
register_startup(app)
@@ -174,7 +170,7 @@ def register_assets(app: FastAPI):
@app.on_event("startup")
async def vendored_assets_variable():
- if g().config.debug:
+ if settings.debug:
g().VENDORED_JS = map(url_for_vendored, get_js_vendored())
g().VENDORED_CSS = map(url_for_vendored, get_css_vendored())
else:
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index cc937b5e9..2ce916127 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -6,7 +6,7 @@ from lnbits.settings import Settings, read_only_variables
from lnbits.tasks import internal_invoice_queue
from . import db
-from .models import UpdateSettings
+from .models import AdminSettings, UpdateSettings
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -26,6 +26,16 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
await internal_invoice_queue.put(internal_id)
+async def get_settings() -> AdminSettings:
+ row = await db.fetchone("SELECT * FROM admin.settings")
+ all_settings = Settings(**row)
+ settings = AdminSettings()
+ for key, value in row.items():
+ if hasattr(settings, key):
+ setattr(settings, key, getattr(all_settings, key))
+ return settings
+
+
async def update_settings(data: UpdateSettings) -> Settings:
fields = []
for key, value in data.dict(exclude_none=True).items():
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index d9d2b22ff..318116594 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Optional
from fastapi import Query
from pydantic import BaseModel
@@ -55,3 +55,7 @@ class UpdateSettings(BaseModel):
opennode_key: str = Query(None)
spark_url: str = Query(None)
spark_token: str = Query(None)
+
+
+class AdminSettings(UpdateSettings):
+ lnbits_allowed_funding_sources: Optional[List[str]]
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 7d268301b..103912615 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -314,11 +314,8 @@
}
},
created: function () {
- this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data
+ this.getSettings()
this.balance = +'{{ balance|safe }}'
- this.formData = _.clone(this.settings) //model
- this.updateFundingData()
- console.log(this.settings)
},
computed: {
checkChanges() {
@@ -416,6 +413,23 @@
})
})
},
+ getSettings() {
+ LNbits.api
+ .request(
+ 'GET',
+ '/admin/api/v1/settings/?usr=' + this.g.user.id,
+ this.g.user.wallets[0].adminkey
+ )
+ .then(response => {
+ this.settings = response.data
+ this.formData = _.clone(this.settings)
+ this.updateFundingData()
+ console.log(this.settings)
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
updateSettings() {
let data = {
...this.formData
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 63ed5b3ca..57d62ed49 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -10,7 +10,7 @@ from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
from lnbits.server import server_restart
-from .crud import delete_settings, update_settings, update_wallet_balance
+from .crud import delete_settings, get_settings, update_settings, update_wallet_balance
@admin_ext.get(
@@ -21,6 +21,11 @@ async def api_restart_server() -> dict[str, str]:
return {"status": "Success"}
+@admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)])
+async def api_get_settings() -> UpdateSettings:
+ return await get_settings()
+
+
@admin_ext.put(
"/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
)
diff --git a/lnbits/server.py b/lnbits/server.py
index eb7c12b16..ecf7ff62c 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,4 +1,5 @@
import uvloop
+
uvloop.install()
import multiprocessing as mp
From 8fbf10909961c13b6c39008ae4ce3aaa750b9a51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 25 Oct 2022 09:25:37 +0200
Subject: [PATCH 134/134] fix poetry lockCCC
---
poetry.lock | 24 +-----------------------
1 file changed, 1 insertion(+), 23 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 459683900..bbce3c5cb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -258,24 +258,6 @@ category = "main"
optional = false
python-versions = "*"
-[[package]]
-name = "environs"
-version = "9.3.3"
-description = "simplified environment variable parsing"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-marshmallow = ">=3.0.0"
-python-dotenv = "*"
-
-[package.extras]
-dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"]
-django = ["dj-database-url", "dj-email-url", "django-cache-url"]
-lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"]
-tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"]
-
[[package]]
name = "fastapi"
version = "0.78.0"
@@ -1051,7 +1033,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
[metadata]
lock-version = "1.1"
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
-content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42"
+content-hash = "e798b36b5941b43ee249bc196fcfb28d8ee712947336d21467651c672ba0106b"
[metadata.files]
aiofiles = [
@@ -1307,10 +1289,6 @@ enum34 = [
{file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"},
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
]
-environs = [
- {file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"},
- {file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"},
-]
fastapi = [
{file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
{file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},