From b5cef680d688e972619ec5b1919c2724b962d879 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Mon, 16 Dec 2024 22:50:51 -0600 Subject: [PATCH 01/13] init --- .gitignore | 3 +- .env.example => backend/.env.example | 0 .prettierrc => backend/.prettierrc | 0 CONTRIBUTING.md => backend/CONTRIBUTING.md | 0 LICENSE => backend/LICENSE | 0 README.md => backend/README.md | 0 backend/bun.lockb | Bin 0 -> 195711 bytes backend/next.txt | 7 + backend/package.json | 47 ++++++ {src => backend/src}/config/admins.ts | 0 {src => backend/src}/config/config.ts | 0 {src => backend/src}/index.ts | 58 +++++++- {src => backend/src}/services/db/index.ts | 0 {src => backend/src}/services/near/index.ts | 0 .../src}/services/twitter/client.ts | 83 +++++++---- {src => backend/src}/types/bun.d.ts | 0 {src => backend/src}/types/index.ts | 0 {src => backend/src}/types/near.ts | 0 {src => backend/src}/types/twitter.ts | 0 {src => backend/src}/utils/cache.ts | 0 {src => backend/src}/utils/logger.ts | 0 tsconfig.json => backend/tsconfig.json | 0 bun.lockb | Bin 194589 -> 279779 bytes frontend/.gitignore | 24 +++ frontend/README.md | 50 +++++++ frontend/bun.lockb | Bin 0 -> 121229 bytes frontend/eslint.config.js | 28 ++++ frontend/index.html | 12 ++ frontend/package.json | 35 +++++ frontend/postcss.config.js | 6 + frontend/public/vite.svg | 1 + frontend/src/App.css | 42 ++++++ frontend/src/App.tsx | 18 +++ frontend/src/assets/react.svg | 1 + frontend/src/components/SubmissionList.tsx | 140 ++++++++++++++++++ frontend/src/index.css | 21 +++ frontend/src/main.tsx | 10 ++ frontend/src/types/twitter.ts | 19 +++ frontend/src/vite-env.d.ts | 1 + frontend/tailwind.config.js | 13 ++ frontend/tsconfig.app.json | 26 ++++ frontend/tsconfig.json | 7 + frontend/tsconfig.node.json | 24 +++ frontend/vite.config.ts | 7 + package.json | 49 ++---- turbo.json | 18 +++ 46 files changed, 678 insertions(+), 72 deletions(-) rename .env.example => backend/.env.example (100%) rename .prettierrc => backend/.prettierrc (100%) rename CONTRIBUTING.md => backend/CONTRIBUTING.md (100%) rename LICENSE => backend/LICENSE (100%) rename README.md => backend/README.md (100%) create mode 100755 backend/bun.lockb create mode 100644 backend/next.txt create mode 100644 backend/package.json rename {src => backend/src}/config/admins.ts (100%) rename {src => backend/src}/config/config.ts (100%) rename {src => backend/src}/index.ts (57%) rename {src => backend/src}/services/db/index.ts (100%) rename {src => backend/src}/services/near/index.ts (100%) rename {src => backend/src}/services/twitter/client.ts (83%) rename {src => backend/src}/types/bun.d.ts (100%) rename {src => backend/src}/types/index.ts (100%) rename {src => backend/src}/types/near.ts (100%) rename {src => backend/src}/types/twitter.ts (100%) rename {src => backend/src}/utils/cache.ts (100%) rename {src => backend/src}/utils/logger.ts (100%) rename tsconfig.json => backend/tsconfig.json (100%) create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100755 frontend/bun.lockb create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/components/SubmissionList.tsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/types/twitter.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.app.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore index 485282a..45a32ef 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ next-env.d.ts /playwright/.cache/ .cache -.db \ No newline at end of file +.db +.turbo \ No newline at end of file diff --git a/.env.example b/backend/.env.example similarity index 100% rename from .env.example rename to backend/.env.example diff --git a/.prettierrc b/backend/.prettierrc similarity index 100% rename from .prettierrc rename to backend/.prettierrc diff --git a/CONTRIBUTING.md b/backend/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to backend/CONTRIBUTING.md diff --git a/LICENSE b/backend/LICENSE similarity index 100% rename from LICENSE rename to backend/LICENSE diff --git a/README.md b/backend/README.md similarity index 100% rename from README.md rename to backend/README.md diff --git a/backend/bun.lockb b/backend/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..aa9bef5116f814d47c8f3b16d519dad606a4dbe0 GIT binary patch literal 195711 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%0D~`aGd`F;A;RMs5IQa;5fpTkpA;YGW(BZ zjGff?WrXM%0U!52Eca*qMFAhzFByAcmtFs-fsglpj2+hxJWjJEfBBJOjvtcPo%^Uu z_?p1S{9_qofFfH$_^W|H;)i+6wh(?k@a4fj_8nd&vMq$)418@We;7ZzLij5u{_pvp z)c0kF=sg8K>-mS>7zlro^xxm#WB&*A<(O(Gd{5x(QvJs|cK2YyA2o^j{K0PABs{{m z2R`m!q)d?L{TmX!9l+OR89!40Z#+^@^zH$l-2XZ01K|&q`TPDw>WGiuA<@+Z{#+{l z-|7F_M))znC+81##~y7F{w3h!^FQf3!C(FTS|)r!*}vD%uR6ag6TUIll);lFaBXe2w!FL-|v61j@0*Mg6N$9zV#o(&pYMs{ReY^_ds@p z__qc=>+>5sT+|`_L%_%Ulky-o4G{ef;FJ90*^`W8UuFn@%2XyF^+{b{TvlBd;FI$= z`exS#(c8zu$MwhQ{HX&z<_}}yfs5TWO#F|4!#D1q$j97snm;4plk<0f`X2^-W$=&B zzBu#%Ii!vgehcu`sQ%;F@v%c@@udqdZ*?g? zo77%466*%w>jNLlq>ddsM7I+7 z?q9ffV*H%0pL4)x9X~3(K7qvl1^Bpsp>MoMeP0HMo&&u6S^#`9Z#kVmIlw3WiOs%z zV>{91hlejymiVy`>!mU)TQE|03@lth*Ue_uLnNaKXCsR z;gDYfd_4b%Qd~~wKNsA5b%2j&hhcDG4D1Mr-x&C~elYL2|B(8=Y!JOUF!=cVj&b82 zMC#e)j{ve2@KL`%_rGZ18&iCo0~iB4LgM6s!6WO3-MNSM3Evv{xc<>M&V5pkI=>U5 zcL?}o|3Q6D*H0(#$@44n$v%poe<#HMwAug5$9}LQgzp1<^8OBaL)amM4&mqiA-@Iq z`jCIj8@bpCf%u;Wix1~Naa?6NQ^U9N~KTBnN$Qv7PXBfe%OUzve&MU`Ghw2l%*uz}G;%HrZW+gs%dF z2Sus$`N0vamrv?g`}uF5iLM&(VGHY>zx{dsF9ZHk%0K$zbpINyO{19rpY)yGaU}6a z0pEg!PwYS_zZJrNpw0aLKpeFD(|=K&zkmNW25fM8{;>c)Y(Wv^+$QncsQ+#x@uvgd z5ctEXCQkd`41AnFn0GF?h~2+E{qrZHr=rWe|8a75LyLqT27I!9{@K$%*Ajj{@X7jT zx9_a}JAe=W^sXPQ#~iRD#Q$`?zwaOY+5i3ZX*3(~kMplT{2RcZNAdeJe}@?`e}CCu z|3l}}Xzt)2^TuxOFux>!r7(GM{ITz1*ubU`e!+bBZ+%ky(ZJ>O{A~}1XZt_kp94Om z;IH`Eor5HPZg}~i1AJV6qu?U-eWiiu?F9Z3;FJ8bI|hW`4SaI`BsTi;jqQZ50xu76 z{IK7ojuXBQ@L>r>5H3DLvLi(QB=F6s{^LCyrUP3-_}q(_>xblR)b(rR<8SQ{{(RuW z5xDpK)SvvV2R`2aF@OExR{$R#;d`IIiH+X|PBoJFyMgZj{Qj=LM0oghrTG0>e?7oo z_6K}xI6P>x#Eu zd^|rP2e~)~>OcLhFcaP{P_Nb-RDrk9}5pZmK47~^EUt= zp8mjpC-A)~KJFcy_J279zw!_GwZQlN1HJ*g{Pm&ud=NjoYk=hc6!0Ve!2e9xd^Y@n z|3ct9|AGH8OX2_3@dy6b0H5Sf4D!dWko+|R-;eU&pZRYChX*qMaQ_ovGXUbh68JEL zz57Rh=I?lTc=Kf8b2@)E1K)w-_h>ul5K1uLeFmg7^FVnrzoNbV%wKoljNLxFAAr84adLF?@SBq{U!0Q1U{a>*zGs5PWVTFPo7`M7!sY|A>p?JUkCh) zgEHojT_Jo+CmId5z~1@G#V#522tObAxc}q2K|Z@S2!Dz5-`_v8%f&DW|0eKfQTfOF zH@h|nU&-a~?~l+fr+hcy&j$a5hi5o;g!n%Se4PJ;&+a@T{5QZi1U~vCHaPLGz+ldQ zTtDcWT^q!|JMi)Rk9@4-lz$iaIDgPL@`+ve_*)@*(yla`HcS6G%^!o}IUwr*b$%y=-%0V2hp}_YSA~}c`rw~k9*K$g4+lQ${)6+79U=T8;N$!udFN+` z3_67W5%@TMgpcO{c7*UJ!pj?+e`M@9-G7$>AN!B{7IN9mBk{it_{zZV@A@wX{-QtN z3&YDpvi?X8Imw?H@Nxe}PJj6Ez{lqo95>{%YlGyE+wXt>`!N^?J3{!Tz}E%;SZ3EZ z+9&)Sz}E&onLq5>ApASP$MsLvFQt8>jit0zR%EGJlC(;^*Ix=+y(C?Emcg zMw^5$8u(}D7x-U>{?UIwuNe3m(EtAKUtfT)^9TIYAm;a%{at@t!GGVsaSpIM1|^E!U+g#bfz$pM0AG)apOZC6^3S#Af8Rgi*svpn zuLgXLKgiz(;QvYfOM$QR2mU9l{rlfP#XgaF*jEln|5pHiF8C*9PWdIkp9Opz_YrV$ zI)5jxWBz`T-FZX$MdFPIKHmQbpVRw)74UKYKzl-P5j(`szajB29KoD_Wc~h|{;&0f zZv=eYKg2=^55V7 z4~+VI|L9NtEP<~N@#DP5_}LW_|32XV$@q<1|7YL-djOxD-^m!T(+3j&S>XRk{3AE~ zy?^v4e|Eqp=db?o_X7V<_TM|eC+`n&{^LAgM@argZ)E=ak8~j6{fk{8d;{R){)N6t z9XmFNZXWQBsP%_+7zaB-_+NqV1bi%G-`O1l!ncWLKK~&P`J^5`{#Jlt%f9Bs_;N$a0f5*QS_K9F&NkG~b7=LLM$^9!f*?=bMm^Cvpwbo}aoKLhe70$iL!Brde|J0W`VTbTO~ zmT~QJx_(yze=hhZ_BieTdEn~+AD>*yTZ(y+Zn%1AN^7 z*xfgNho@8KS2Sd{yvI@{avrM+iR%__+UJ{?G=e`~Pv^ zv#uW!M_(}z{~sy;sE>7=`qzwS_8;?4))3n2ONjpv;44$(N9y|GVmr~z0Y2IPF^7We zkXiVzfRFn>#*TX+?j!67@$a9&JU=0S7+jp@?>+G6fqydh*A^!7$kLPFXH?ALyfgK_IH^4Wf`Y!}TPW!LD7aH0GTG5$2T z5D&qH!ipGII$Y>C8!nXJA?hFQ)nX#rJqj20BbTa2M7`rw8F4&ZH{rtiTX3QL4l$lP za0$Tm3@#KzD(f}9c;Wb<+>>%>r^lGz*gFv^H(*GyKarp`t+U=(F5HbE9s*H$v z>Q$l;^||m5h4_&h{$PIwQOFO7c7(B!LOiR8QDsE@IEE_!6Jpa?s+|>ayv9R4#w9_a z6d?MO0ptOc2ZVoU3h)Q@l>kwH28GJl2=Y&edb6nZ-y!PFrt}bd_W`P`%Yqo!T&j$S zJVUDdPl!$PsdhyCXhf9}u|JEbdSgJeXG*pI6QchmR69F}suu7E(URh^BHklvE)g$7^JrwQ*#CHIv0a5=9Hd2W1b*@8M z7_bfy``ZYJ{cWT4SP{p$6Y6n(e4^UfK~()r>9Zo*>!#`vF~0ABxUUR=0{Y>>N(wQq zAyCFD0jgb?YDYvQ>aLx5`{A;RHkqig|jJCqfi47^QQxd{n7_S`6om@L#iDS`#m2J z_mjm`JtEedQ)O1fDhsL}5%nx7w4%_OYX2w1dF4j6BjPxEQRokdO@a7_LbM-5mH!Em zA4;{eBIY3+>XE+&5LMPudaQ`+ZX;FCideOYsz*dSF@RXLnW|?+yua!^A}ydUhO%B+avlL+-_Cy8oDME(J)%!;U&Le(Q; zp0fbaP7WZpAEPiA5aY}PgnwxH@CWUl!2*T2&MrY2?+e!eQNN06XGLtkL)HHiV$*%9 zofWa_A^bsnOtrHj##;~d7*_)zmY-Ajg2I;+z5+x+M7`I581Gv^eTS|9ejU?>U*d7XImZ8Q0r?&&kYv=fCGQN>0_S#2#xMBabMk-B$^Shk zGw;*?JtzP7oQ(I;KYvc{5$Yii%)LAA|IZ&T8q+STX?n=<*6)i3+@?D{4-mZ6wrR%l zqSZfZHIHSEuja9wzG34)&l`>o&(?ESKQGC+WNT|`pIUP|VT{5n*9qQR=nqeHOoh;h zUF!DG8!X*pPlM$o&-F_L{Cu~Vo+(Kg;UTTESi$#=z5jy)C&p}Z4)vIHx~gF9rElHe zHtyr8Gc??Pcf+v@>g(SJnyEEd zLiY`8%$%O}Nh|)OX{7LLk(USeNS%E$=jO~MS;5~PD2q7M@mIanxs_`^WMk!K&j|Nn z{ws!yOXiqg{BnPL*`rpGv0#Yg0pA%Bp}$)iSaiC|#rxW$yqilVNUt(v(@!^SlF6r3I@Ep17Fr0zH9IDQBq4rjBZtpf^2TE#V6Bw=YQX-Y9s!>Xe*V z-hq3Xizn-~pI4IsL&Pq=gCat2bWIGq{9L~*^TETM$_kz3%REk`V9_9>&h+TY#MuaX|8Zl>5)2)0t z#jNzRL4oxp69=6-q-55#r`9!pPwkKCK8tMfRV#BW((H+X-n@Ym{cZ;lLG_cr#{ zdN4%n;%_;K(BIeHklFkyGX34XtA#hTzF42F{5j*vfI1b)DMqSl+4~*pi?gm?QX4a< zG+@sURXK_1n~Rk6a_-&=jLYQOr!dO(9T*~Z@f{-(`senRlnFV`eqWb9rt61a9Ts`U zLAoi;$6#TwB(K6yiQ;a%OOFO_oqa7&e~QYuop*~aH%yCLapB(HxpaDo-o5>4V2Ieo z@92rpg*(3H*zB#eou4tKYQK!~{?MkU1NOaB(K%Sg8?$w%$ApMh)7bmEdP;}6Z^VvN zxiTtT!_GbLr@WHzhfy&;CtCx-5V0#ri~`c5^{w77vW(kq*HJP+#w+3Hz&%%vP99pu zWp42-p_nb^k|MlTYSFru*widbXb2J%8!!1-GZ%TYe<-w0pZrVrbg?oq~ER%S~oIU*A~x z*2#N!;kPLJ6+TX1qwK&Cv5Vg^5~1(>R^hQ$%gOSrjd~p4$C}w8<0h%qi+nEC|kMoVcv%Qqjt=wuP}-lxazvq)DeZjo025>QZ9laVi&(FCPJ6>s@r(0 zu}C+gT1viab3|C`&Ly?iwHj3vyd;Xw|425KTQpfru-X4@)_T79O>*kbzH1cIBkxK( zJ1z7!q9rEaoF#Vg-6|3KE9G0ULr)Y*jMe)Zl;E(jQQ)N??Mi9#;-nF=0VcPO8W{Ms zw4D}T*^sd~+)ha_|I5d6x114I2NsA1s2FZk9a9H}h+X_G6%qRA(QgJN3RUjY-Z)cy z{;oUOkN0UzeSP=NVaIaeGi7UX`2>^IG=kQqu6a9l{F}7_J@L&K+g_h2o;1_3fp=YE z(AIfih}gw<_eAI$mPt;U__{gtZTABCu@2s=2kw{>G$eb{r?)#cZER59yUypr-SY>W zWV4s8EUJt5pSSMH$>1fY1YfMZC3SSQK~V$FEn*kHV<1A0uGuAM8&D~EBQkx~l~pr` z=+-_KODz&Sa(ADu@RpG!6E!`T$~t7U(Qn+IH8|h9v*BFAjg>~9 zcJaFvBJ}7YQ`MM~4TC z^yZgq3S})fBx)pY`p1089LTd-<(=f``+924;RT!C#~G$*sRnE8bd}C`@VF*3F#Y=& zMdgHv!?yxJLF0oDL`njEVbU{hthtLaGv~q2eCQVw&qoicsrI=PHzWsBHi^Jy2kLAwi z58kgJoL!#sbLoVhq2kxRoHP+ua`O%?Ukrwb-EqVyAU$-+3cgPaM#+i#6*rp06qTj4 zR|yzBI3hN$^S1NPkn)A^^-@1MtB=`j(EZlE(7xKNj4R#d!J4Yf@14&C-MN=9WwDFD z1tUTaYPnE9WY3e_6Ut}iNxmGNIcq|k=N9vCk&54v{kN}~9a5ft^;tvvbjuW-b@Nge zRBbr8d3H{ue`~GA96FCZO->sOk$5K%qkwds4$*>VdBbbuhY#ZJn2_)EWVxf);={a~ zA6;JGJndq~llrWST-+c1v+Hk(2}b{rxGEhn>_c^3b*BQq!;lmAZE$W8yArH++uiPj zty);__((~4-l_>2vXhSpM}Js0Q7-OTqzLlMNz2kd`L07`Q@F&ey>!x|RW)6zgVzDd5YIpPT6pO`kUIYm*8ZqTz zj{bVLkRs!%q1NUyLk2yMs}xU`sIVTruew9x*6_5M!*=;ro?kqbe(?0WDUVVPOermN z@n^9+k=1TX(B9|tF%45E2S_$9v7K(v_Cqb)OCq6MPCukpM?~+uyL-3z$jYjAAMJ>p z1G%E#rENaEYoCb1OZEFx>=zrkoMW*o&1&~d#ZF1xJ)bTJba6?2mDc|1KL4iu&l{Dt zMs42{^Y<7T*V~rw7*}RAoFRAn(}|qR)29Ybm?$c;{9y8y*iqh(D_GC#lUVIu*c&Io zeKK%n`OgI^gKv)zulN}H!oE?-`ATW*+fgIiME6%5ttgRwQ@Tl6C_*c;(P-Tir90g7 z%ObCQ?R*)UuFh%~&%#9L!iuL4>W#WDmetUBz4No~-0hmm z`=zAYtZBfH>w_B~N-mtb{ph!QDu<5bh$hUqYx*D_43YVOdj%2tV&P}w95fz9NUQjz zMz1(+@p8fI6OUuJr?pjtNPcLse^aqTYS~Ba3kCWuaiU(1+A}_!X_d=*4^xgf5^um1YG%NwE>4KQ;p?VKcRGiv#o?VP>Fq1brC(lVs{#=-Lx{5;OiYtt)tcrHMRHddUVog`Fqt9zMkIdw>~E?@gM)RL*R?w>O7-| ziE3Qm*4}vKY8hPpZP`bftmdMPFAfQ^J|8Qw+VxdWy5Fu|ezzdzj?I+7%<~(Xtvg=7 zOfT^A9`GbUrnKEVq5J)@XHsVRFGiTY+TpY(YR$rTGL-}D?i8-r=6UCE9!tE_S?yW` zI0xRkGXG+D=bO|80gPR$irl{Ye@tFpZJc?i@N*&0tQSQ~O7y(De@@GN7n5|ofm^k5 z+yPJfUFEw@MVI?pvYyxRcko2$OL~6T>AkmdI25`5%T_O%>5_~R*Ug7Tz7<-;@#r@|CiZ?mDs)|c*>p1r&Hlh0aYDwTFs-J%-?YcDc zzQ~j?Wtoy!EOwPy?I!xKw5V#yJLk4NT`TuxenowT*P>h2nPKPoo31H+F4!~d;j7Cj z8hjf?f6yg#Z;vQ$$qm##)9|u(Y>)P(z+TAI(yPIFDzEsO|ZhWX2x z8^0&-9RBs?$jTa>$<{v-Vhz^j2&E?+m3D}JvNPFEWM9R)hgUs$gDY+Pv`z?Vu-L_a zuYd^sz-f(k>qHTS#aUCN&o+fNR|*uR{uF;XC^_t?Nw|5p;^J@|&5NZI54oA(CHJVib`6e&!9YQ!>dCryIvvPxjo=b8v3$-H2yD z8Ozr@@MH+(f8LX*Cf22uP}VqA*zal7-UGL{*IcYDv!5QPxTL^DA`$mSVi(WCMChSG zzT=hi9Xfmlt=FlVH_4H=V)Ol}XO4}zU|U`9vwK9KnNNkOl&Ese=j*bo!y{9#+I0ME z-&QBMJRxCDT7AY4TnohR9AXrZ-c+(y|7qPEdACtFqMnBo3Dim!&kr$kNcP*?+8X0; z&@fk9^A@dI$am}( zE%tS(=Z#6Z4R6*ga5nciUm5m6BS2cYc=su7;oM2NhCX>gwW3EJs6Txqyv1!(`10_g zb2YfGEoNr&K4+%Jfg1C4S(c)l~xvz8w?d1>CO}Hag0i z4qecs*Sd7bBJ0}6c|#nwcjO0N*(GL!?+%GwoEt>wa~9PG_xP&D>Kv!#8RvyXj2>M6 zV#%;sI_oE$^4wl^`fJHGrB_?Wp6l9wv(kSxJ><04{cYNtS0?WncqjCV-wQt68;M;_ zVib_x`b6&T(c7QTh)FoU%IvHZjfm@cG}NL_Xh>Zm7u};`onBD7rOKtKJB^xOkBr?Z z+4Lae{UayIl#sBcVlTL#W^ZM&i)S?=^fN92vCm7(LXWE$YFRr5$Z8%MpOriI^TRu5 zLg&ng+5F)6#_d8;si!&`JYof>6uZp9ci1m|w7@YbFk{B z>+VBLJtIR(ZD6kaZC8iYu5IE&#~k}jB6SHNCDweBdQ~w4r6cD#%BV%2+O_H2(cN3R ziU+-TdQM5^)DW`{lax7CwSy$T#K+T`2fmru>8^5@#jY-^-F%+2(^jp?rwcr8n*Ygi z^tZ*;7Xwcg4%Hcu)I2ZsUHF%_m<0X0(JxP?ho5X8d@C?()Txl{c$3w;wa(P022|Iv z?mKu-B0^96L|5N)m$!1i*i>g>&zi$;&v7Z-YB(Y?MC`!bF&loYO|!Lgu3l!Xl+?BB z{y1}us1MOH77|-%H)X%f5@_5e5d(%ue)Wk_Kzh6NkxaQ0IeW&mf3t9wNKP0v!P{ie z(>Al}l+^LLQlqS=c0TW@sNQFoeRQ8qjMa>p$@96|MWrSy9M$*|8wgvG7_tKIcQ zSKsvs#Q7ee#;J%L#0cWNT(UB zT!in;NPZ28Q9$~XBn=~lpp&Ty;&;K{y9Uyhf~c z=j>T|<7c{wd1XtyOqi2Z19ukJ8nxR=qckjbf9Kh}FllXp-_9}8c0()+4NFDLc<7f* zo;{1UdavJ^@buhBz9uw9>@HxnJ8Vb!@`9Y~TSlAhZ8j!%HEBuBe)De1@%htT`JWD) z8|3)?@-ma9!TcxcUaL;a*%7x;_NlwV9rhX9n+x zjLm;^PuxZ_>OtNr;Zsf1s~_If-%37bCE4kxs0F%kHDqlYMJs&PD7U`f`)PzK13(cJVV2 z`ojd7XIiv*M_*38oYlGc_7KB$-q+R%9{N@=?8)d9-g~l7Mm`stR+M;Iq|^SzmJjjv zMdeqbJKQf)4Fdu8la~)dL4F z8((@*=`zi5f|Hr#+RUVa=QM&t9PlE5k>IM<7-6~D1jW(sEos!Fo9sg$B zf!W@{QuYfmMiMW+>mfoP)Wuc!aJG@uuw!4jy96%ydyAY>S90>`wusZ|nf!X-)WvVY z!!8HN|9EFKcPM{!&au5`&MC~4bzODoRPyukVF|Thh}bnJMgi$|KV+7+Z9m!aLbU1v zcdE^1=`s6~Rx~QE%Wmsv-B|rSVP15>00Bck&*rjOQ+>E>bpR2Psu(t`6tCJb}d-#x))zPGxk#4+Ig#^L&n#R?(W&Owbb2z z%$~;mucQa$n2fd`DqE&u|g@{8777%)j! zNbF)=(rD55r;ZHVb!%5j@#_T_e1|JtpQ;?4$8&UG;Lvm4{9U>gZ(nSri!>INO@9_R zm525D4d49{p%0ARQ|TNK+%aX#*~9^h-9IP~QM9YS@~-@msb`I_;qq}dG7ZMhr#}s- zP`+~S;?UeV@zQ(6kH4~t38(Kb+tzK4&%|V&Eh9z&>9zqb%?nb8y{h;rgD(+a-8Nrs;w0w)CfeX&*Y8-m_dhndC z+gFCvwJAG0O$+%vr}<+|dh!U)$riCkuS|G zhB`M~6lqi428M`Tdtwxj{`g{3M1}S~+2A2+Yf=@O^0G7Pvh7Aj+U`CzKDoS0n| zb>xS?=lWEYR`ZtK{@R=GMZC@xzpHn;EB}(CO{{!~eyFWJurg<-?qm5P(|siqf?uAr+O<*qho~U0 z)Pm15V z+o^3WySmq4;DKGjQ)g6H)U+j*Tzd9X!>vFoU7)ogds{`WQ;qq%1&8=7U(R>_yewaq z^?uA?wHxfCT(Wz){OWUtiJ`u9*J~TcaBqlwQthKVZcFF*>t`;S34c1`y@qkz`dT`S z-z}M>%F+{z(Vud~Y7U%=>^`%JC0oJEXh2Q%V}8>%bY~J$K*vXF2%sILF+z)?6EQ7*+}Q zO};NN8@fTXIBqFQOOj%SafD=at+(B(Bhq>)nN9bv>k(`|7)?KXzOUBMRc~`W@Ofqd zbiZqVNkHj+4=Ov6}28hsKSqEyli!UrZ0l_t@boL zu{ty`kM!nE{Evtvz6k)=66k7;()?>uR*6kfE>yVxXOF=j`gfYlJWXACi5Lei^H1+D zte_vrL0*lqP>{!%#P0k^Lk8YLi1hb&?J@QCb<_m_*9z$3tTGbb{0GM!{4FfL^niM# zx~}{--yl#U=K^ji+bRiHGoY>DmoGi>`^+1D5~9(d5uaxi+h&~&je_5^k~3+*>&+VI zNW>8gM?OsY8}h1P?Y0+HQ7d-%gX{0 zvxkZ4u5!GwCr|U+6_D@SGZY*kZmR!^a!@_Dswyj~@xp2{@0iFF5x&pC@{y%Ks+B0= zGI~m$l{XQq;>++3n|#Yp*I+~VY29D@95K=p-AeZatb;8WeH}samzqDit=|=CWg4ZV zJ?smaa1?~9aQ>R4?=5cX?%``ZBu(8uc?X-maU{?4{NIsZb!kupB#Anw5tNCz3v@Mb z9cu@4m%AmYxl_qP;}=jVs3|RZq%GKE`imy^&jlHzDI6~lL$uu=oHxm`L@r>nNUSu` zJkJ|aoy)%YSFf&?iNF&8`?U5zcQm_LB#7&>8is%0Krj%E(kLz|j!cGxrHRb))moXltERv_me4}M#j6EAe;t6XElY1oql{(xge~;* ztSqz1eR_5Ky0d{}RGwi}o{WMNN&%v$^8=FtgMVQozoZ^ zQ13H%X1+aMh^vRTd0U*9sf1JO+F-?KK1WyJ zw9Y71U#%pvr0r2SPeu(jY9f9;O_zkJ`WJ#c^yuzVV-AMCUL zba+mUe1M!d;7QI>Y4^ok9)ICaTCyaP9Vm zjo-QN6$0164UE2ypyH1WcHt^D`{z0;RRR-5Ydv1C$1v%?|0X1fslkMP=Y+EA!le}%yLzMae90R8%cn;l2g;awHa1u~Ba zha55Us?*fp&*me(>Ot_*&@0tDMUR^Q(N0^!1ykaswkicL%BpIg z6X5y+T}5%($`x!S47l?F!zQZO6RlcWrm6RXJB!)b1tvDD6~+4-2qb_nu+d-R+eKAM$hC!eu%0GR^26J9iNP?7(A)Da@2TW3;G9G zbm8q-+RkuWrFsl5GycH(Ps}B%9(Wx9HxTGP_%Tq<##D|NS_IQjEt|2+TYqb8W%_Yu z4wH?Au9&`&6OeQgUFCUfQ%C&nruAz4M?Lo9s%OsNnVdoT^)YENzzqVrW_~_iKa{)v zU>g+rsu3Kg;OgwF8vpK{?2c+~aD@(Nb>@^33A7YPoeLFu)MfDfUiNp2zjOXC z+?c1E`B2VOD!vflzRj`00s27*sy!3FKjS08H`7Xxi0C%_$g7<3@xXY=bE^PHcQu|Y zJz)gb;yi$=DTr@F=|SnxVTuFJz58D5etCL|?(LonuESfe9UP#|;pp&!rx}{d^r+*O zH7HU`UVZZomZc&@iM<93J!B>YxWhQ-6UP_BsHaa49jp_w&kKtw?@q|Fqz<+PzE0M? zLSQ!njJ}SbX-u^%ve=H#oMgD#I0Z`j<}GfeUfNUt90LVLHf!XD``4Xs&vB`LR!XIn zs;i4!3VfE@jk!RBfIMm3l;y)_2e^?y_rz(e*Y}U%s^p#tTfek@IzRhkHwITv9%`FL z)ream;`N&G;lc1!(s~~ZxfNo@^LN!c+z#FdX^g*CL+lp~Z@q7DzHiqaI6!lqWA0FH z)aB&q?Z-hu#qvpq&Zv*_a){KclpNKR`S&V=lc!lvgc5Ue)?8w5epkQmnjYM zYskl2&SXPf4-Ip2V<~B=j@HkOA_}`hz3K=F^EiZGuAIqADSa$iFbdUpo122&w_XG| zKxWTUixqY&cr)!a4npP{OrlV|s0H`vG@KmN!&yyzoksJQ$+@Hgy3DY4tc8B5YzWE{ zW0N6aX4CMhYRoKIwXYD^jRm8xBgpIPlrP+@MdTP>d*d<;nW17F4^z&BBBC3+4es`g zWKe2GZ60>s<|cY}4Obhc%l*}#Y$L1q!Ym2_4U`I+6yWnX4(Q(eF4zzrY&}J6^NjYc zhh@@0TD-%Spg~r5cGf?0%TjYlj%*Ib8^Ofcr6aA3-$78q5i}?0IX-y(QL2&cc z0_PhKbYXEocSg%2kVZ>v8b))SSZ9#4pnXLwkus-=Jr|Z~t_WA%#bv_ZC1mcUhdTPFbIn+SCC%2coikh_z~mi8?Y zuzB~RNp+}&TJ*c)Qh$9ES(X2hZ&+w$cc2!oircC`V6B|qcg2Q)yhPaX!PwDap1!>Y z;3fgxVvyc8rpI&7XDzCPe+VUV-`Lk`7^I+M3l$`8{sLu=Q40_668IIx%$rWyg~)hR%?0*jTB&kB0v9_x36 zd8FX2BVKv;Q3C3y%q2zE>z0BQT_x(m-At-l64M>%{)WA9bTk*$8JWi{-`((=ZTrkE zAm3D=i%l_5kNy(qDH=}R8=f%UwG<+B%!z2!8$aQEyn@%a<`iCD|{wkHIxLmJRcQDb7OOF|Kw*$H3!sWiUPQj|lxUB~%sfw-5R zN+(?r#qglrpOrlA^UGFfYCgDRf@?AmDesj z*z1jsZt_j63jb@D<>nLOUXNCqPXqP|GJ$TykTCI%HMf1+sIG;DRL5+4&8IMj(Pe_B zB3>^e`$Y@GewF~5|E-iYND*jUDVtuRq&$7LH&Nrkcn@36nqbnV71#Twj<@F>{YR2_NRoPPk*-g zu*wEO@a%U4>nj9yv%%==2)dPED*ZfCHBs=wBN~!+obBrx6mmqQ8g2-;kD%J0j3AVl zMqw?5SaU4OSEbyVH=Vgm-}ASnMm>{ykz9D35EkI(0Nn$=!JI5F>1=;Szd!{MA=5Ed zZ7#y^aXCSFqAPgeLsL`Noc8h%n6Qn#g&rn&dydbpcXQPzhr!gBA7Bfje*ya?xj+{* zm8sRZHnodp)nuwKVLmOy((+Gyi1%y$Kb@!5(w=i`$Of4~)@C22@Go+P-w-+?Y&zzS2we`hX|mA4l5we3x~ z`Mp#BwNp{H81A^}PCtHEA@ecT)^yE5{mbj^Yf?>0;oN`vAMJth5!wdiSjN6}SGt4~`=F+^2bKYQOp%a;)1yHF=Aq6`9}lfpdlxCpF>`8=}G%II=AD<~2S`yx;} zHcrEr(zM}f;#)L7N<}*uX|{yPqiCOon3H>4w!7`TLf|^Q%>lsy3WTD`@F&rL#4;(L z|C-g2ZPil(k0rFtiY@hi_4a3@@~VH|^Jf&Vil@hS9TM+&OIj%%

`aCrK>~d7GjG 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 literal 0 HcmV?d00001 diff --git a/backend/next.txt b/backend/next.txt new file mode 100644 index 0000000..482f60c --- /dev/null +++ b/backend/next.txt @@ -0,0 +1,7 @@ +* Increase time +* Write README +* Test with a new submission and approval +* Test with reject +* Integrate NEAR contract +* Build a small dashboard to see what there is to be approved and rejected? +* Tag all of the admins in the "Received" reply diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..24185a5 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,47 @@ +{ + "name": "backend", + "version": "0.0.1", + "packageManager": "bun@1.0.27", + "scripts": { + "build": "bun build ./src/index.ts --outdir=dist", + "start": "bun run dist/index.js", + "dev": "bun run --watch src/index.ts", + "test": "bun test", + "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", + "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/jest": "^29.5.11", + "@types/node": "^20.10.6", + "@types/ora": "^3.2.0", + "jest": "^29.7.0", + "prettier": "^3.3.3", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "^5.3.3" + }, + "dependencies": { + "agent-twitter-client": "^0.0.16", + "cors": "^2.8.5", + "@types/cors": "^2.8.17", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "near-api-js": "^2.1.4", + "ora": "^8.1.1", + "winston": "^3.17.0", + "winston-console-format": "^1.0.8" + } +} diff --git a/src/config/admins.ts b/backend/src/config/admins.ts similarity index 100% rename from src/config/admins.ts rename to backend/src/config/admins.ts diff --git a/src/config/config.ts b/backend/src/config/config.ts similarity index 100% rename from src/config/config.ts rename to backend/src/config/config.ts diff --git a/src/index.ts b/backend/src/index.ts similarity index 57% rename from src/index.ts rename to backend/src/index.ts index a0bd62c..99582f7 100644 --- a/src/index.ts +++ b/backend/src/index.ts @@ -1,6 +1,10 @@ import dotenv from "dotenv"; +import express from "express"; +import cors from "cors"; +import path from "path"; import { TwitterService } from "./services/twitter/client"; import { NearService } from "./services/near"; +import { db } from "./services/db"; import config from "./config/config"; import { logger, @@ -10,6 +14,52 @@ import { cleanup } from "./utils/logger"; +// Initialize Express +const app = express(); +const PORT = process.env.PORT || 3000; + +// Middleware +app.use(cors()); +app.use(express.json()); + +// Serve static frontend files in production +if (process.env.NODE_ENV === 'production') { + app.use(express.static(path.join(__dirname, '../frontend/dist'))); +} + +// API Routes +app.get('/api/submissions', (req, res) => { + try { + const status = req.query.status as "pending" | "approved" | "rejected"; + const submissions = status ? + db.getSubmissionsByStatus(status) : + db.getAllSubmissions(); + res.json(submissions); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch submissions' }); + } +}); + +app.get('/api/submissions/:tweetId', (req, res) => { + try { + const submission = db.getSubmission(req.params.tweetId); + if (!submission) { + res.status(404).json({ error: 'Submission not found' }); + return; + } + res.json(submission); + } catch (error) { + res.status(500).json({ error: 'Failed to fetch submission' }); + } +}); + +// Serve frontend for all other routes in production +if (process.env.NODE_ENV === 'production') { + app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); + }); +} + async function main() { try { // Load environment variables @@ -28,6 +78,12 @@ async function main() { await twitterService.initialize(); succeedSpinner('twitter-init', 'Twitter service initialized'); + // Start Express server + startSpinner('express', 'Starting Express server...'); + app.listen(PORT, () => { + succeedSpinner('express', `Express server running on port ${PORT}`); + }); + // Handle graceful shutdown process.on("SIGINT", async () => { startSpinner('shutdown', 'Shutting down gracefully...'); @@ -54,7 +110,7 @@ async function main() { } catch (error) { // Handle any initialization errors - ['env', 'near', 'twitter-init', 'twitter-mentions'].forEach(key => { + ['env', 'near', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { failSpinner(key, `Failed during ${key}`); }); logger.error('Startup', error); diff --git a/src/services/db/index.ts b/backend/src/services/db/index.ts similarity index 100% rename from src/services/db/index.ts rename to backend/src/services/db/index.ts diff --git a/src/services/near/index.ts b/backend/src/services/near/index.ts similarity index 100% rename from src/services/near/index.ts rename to backend/src/services/near/index.ts diff --git a/src/services/twitter/client.ts b/backend/src/services/twitter/client.ts similarity index 83% rename from src/services/twitter/client.ts rename to backend/src/services/twitter/client.ts index f3726c1..f69422f 100644 --- a/src/services/twitter/client.ts +++ b/backend/src/services/twitter/client.ts @@ -209,43 +209,62 @@ export class TwitterService { const userId = tweet.userId; if (!userId || !tweet.id) return; - // Get submission count from database instead of memory - const dailyCount = db.getDailySubmissionCount(userId); - - if (dailyCount >= this.DAILY_SUBMISSION_LIMIT) { - await this.replyToTweet( - tweet.id, - "You've reached your daily submission limit. Please try again tomorrow." - ); - logger.info(`User ${userId} has reached limit, replied to submission.`); + // Get the tweet being replied to + const inReplyToId = tweet.inReplyToStatusId; + if (!inReplyToId) { + logger.error(`Submission tweet ${tweet.id} is not a reply to another tweet`); return; } - const submission: TwitterSubmission = { - tweetId: tweet.id, - userId: userId, - content: tweet.text || "", - hashtags: tweet.hashtags || [], - status: "pending", - moderationHistory: [], - }; + try { + // Fetch the original tweet that's being submitted + const originalTweet = await this.client.getTweet(inReplyToId); + if (!originalTweet) { + logger.error(`Could not fetch original tweet ${inReplyToId}`); + return; + } - // Save submission to database - db.saveSubmission(submission); - // Increment submission count in database - db.incrementDailySubmissionCount(userId); + // Get submission count from database + const dailyCount = db.getDailySubmissionCount(userId); - // Send acknowledgment and save its ID - const acknowledgmentTweetId = await this.replyToTweet( - tweet.id, - "Successfully submitted to publicgoods.news!" - ); - - if (acknowledgmentTweetId) { - db.updateSubmissionAcknowledgment(tweet.id, acknowledgmentTweetId); - logger.info(`Successfully submitted. Sent reply: ${this.getTweetLink(acknowledgmentTweetId)}`) - } else { - logger.error(`Failed to acknowledge submission: ${this.getTweetLink(tweet.id, tweet.username)}`) + if (dailyCount >= this.DAILY_SUBMISSION_LIMIT) { + await this.replyToTweet( + tweet.id, + "You've reached your daily submission limit. Please try again tomorrow." + ); + logger.info(`User ${userId} has reached limit, replied to submission.`); + return; + } + + // Create submission using the original tweet's content + const submission: TwitterSubmission = { + tweetId: originalTweet.id!, // The tweet being submitted + userId: userId, // The user who submitted it + content: originalTweet.text || "", + hashtags: originalTweet.hashtags || [], + status: "pending", + moderationHistory: [], + }; + + // Save submission to database + db.saveSubmission(submission); + // Increment submission count in database + db.incrementDailySubmissionCount(userId); + + // Send acknowledgment and save its ID + const acknowledgmentTweetId = await this.replyToTweet( + tweet.id, // Reply to the submission tweet + "Successfully submitted to publicgoods.news!" + ); + + if (acknowledgmentTweetId) { + db.updateSubmissionAcknowledgment(originalTweet.id!, acknowledgmentTweetId); + logger.info(`Successfully submitted. Sent reply: ${this.getTweetLink(acknowledgmentTweetId)}`) + } else { + logger.error(`Failed to acknowledge submission: ${this.getTweetLink(tweet.id, tweet.username)}`) + } + } catch (error) { + logger.error(`Error handling submission for tweet ${tweet.id}:`, error); } } diff --git a/src/types/bun.d.ts b/backend/src/types/bun.d.ts similarity index 100% rename from src/types/bun.d.ts rename to backend/src/types/bun.d.ts diff --git a/src/types/index.ts b/backend/src/types/index.ts similarity index 100% rename from src/types/index.ts rename to backend/src/types/index.ts diff --git a/src/types/near.ts b/backend/src/types/near.ts similarity index 100% rename from src/types/near.ts rename to backend/src/types/near.ts diff --git a/src/types/twitter.ts b/backend/src/types/twitter.ts similarity index 100% rename from src/types/twitter.ts rename to backend/src/types/twitter.ts diff --git a/src/utils/cache.ts b/backend/src/utils/cache.ts similarity index 100% rename from src/utils/cache.ts rename to backend/src/utils/cache.ts diff --git a/src/utils/logger.ts b/backend/src/utils/logger.ts similarity index 100% rename from src/utils/logger.ts rename to backend/src/utils/logger.ts diff --git a/tsconfig.json b/backend/tsconfig.json similarity index 100% rename from tsconfig.json rename to backend/tsconfig.json diff --git a/bun.lockb b/bun.lockb index 9694d2219a50133a044b5ff11f66460565954dd4..7225c1446deaadff55a43c797c585eb70e329c47 100755 GIT binary patch delta 93996 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 E4}h9^OaK4? delta 40687 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 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..74872fd --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from 'eslint-plugin-react' + +export default tseslint.config({ + // Set the react version + settings: { react: { version: '18.3' } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs['jsx-runtime'].rules, + }, +}) +``` diff --git a/frontend/bun.lockb b/frontend/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..218578db0207e56e45308e7e9a41165cbd50cb9a GIT binary patch literal 121229 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

Y7v{>rtag3}Tg691x^H!J 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 ahz$q0#z`zyAYg3xkvZ literal 0 HcmV?d00001 diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..dd74230 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + Curation Dashboard + + +

+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..aca8ed4 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port 5173", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/typography": "^0.5.15", + "autoprefixer": "^10.4.20", + "axios": "^1.7.9", + "postcss": "^8.4.49", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.0.2", + "tailwindcss": "^3.4.16" + }, + "devDependencies": { + "@eslint/js": "^9.15.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.15.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.12.0", + "typescript": "~5.6.2", + "typescript-eslint": "^8.15.0", + "vite": "^6.0.1" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..4304b65 --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,18 @@ +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import axios from 'axios'; +import SubmissionList from './components/SubmissionList'; + +// Configure axios base URL for API requests +axios.defaults.baseURL = 'http://localhost:3000'; + +function App() { + return ( + + + } /> + + + ); +} + +export default App; diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/SubmissionList.tsx b/frontend/src/components/SubmissionList.tsx new file mode 100644 index 0000000..ee878a0 --- /dev/null +++ b/frontend/src/components/SubmissionList.tsx @@ -0,0 +1,140 @@ +import { useEffect, useState } from 'react'; +import axios from 'axios'; +import { TwitterSubmission } from '../types/twitter'; + +const StatusBadge = ({ status }: { status: TwitterSubmission['status'] }) => { + const className = `status-badge status-${status}`; + return {status}; +}; + +const SubmissionList = () => { + const [submissions, setSubmissions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [filter, setFilter] = useState('all'); + + const fetchSubmissions = async () => { + try { + setLoading(true); + const url = filter === 'all' + ? '/api/submissions' + : `/api/submissions?status=${filter}`; + const response = await axios.get(url); + setSubmissions(response.data); + setError(null); + } catch (err) { + setError('Failed to fetch submissions'); + console.error('Error fetching submissions:', err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchSubmissions(); + // Set up auto-refresh every 30 seconds + const interval = setInterval(fetchSubmissions, 30000); + return () => clearInterval(interval); + }, [filter]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+

{error}

+ +
+ ); + } + + return ( +
+
+

Public Goods News Submissions

+
+ {(['all', 'pending', 'approved', 'rejected'] as const).map((status) => ( + + ))} +
+
+ +
+ {submissions.map((submission) => ( +
+
+
+

Tweet ID: {submission.tweetId}

+

{submission.content}

+
+ {submission.hashtags.map((tag) => ( + #{tag} + ))} +
+
+ +
+ + {submission.category && ( +

+ Category: {submission.category} +

+ )} + + {submission.description && ( +

+ Description: {submission.description} +

+ )} + + {submission.moderationHistory.length > 0 && ( +
+

Moderation History

+
+ {submission.moderationHistory.map((history, index) => ( +
+ {history.action} by {history.adminId} on{' '} + {new Date(history.timestamp).toLocaleString()} +
+ ))} +
+
+ )} +
+ ))} + + {submissions.length === 0 && ( +
+ No submissions found +
+ )} +
+
+ ); +}; + +export default SubmissionList; diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..a6fa24f --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,21 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer components { + .status-badge { + @apply px-2 py-1 rounded-full text-sm font-semibold; + } + + .status-pending { + @apply bg-yellow-100 text-yellow-800; + } + + .status-approved { + @apply bg-green-100 text-green-800; + } + + .status-rejected { + @apply bg-red-100 text-red-800; + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..964aeb4 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/frontend/src/types/twitter.ts b/frontend/src/types/twitter.ts new file mode 100644 index 0000000..4cc9b4a --- /dev/null +++ b/frontend/src/types/twitter.ts @@ -0,0 +1,19 @@ +export interface TwitterSubmission { + tweetId: string; + userId: string; + content: string; + hashtags: Array; + category?: string; + description?: string; + status: "pending" | "approved" | "rejected"; + moderationHistory: Moderation[]; + acknowledgmentTweetId?: string; + moderationResponseTweetId?: string; +} + +export interface Moderation { + adminId: string; + action: "approve" | "reject"; + timestamp: Date; + tweetId: string; +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..e9baf90 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [ + require('@tailwindcss/typography'), + ], +} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 0000000..358ca9b --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..db0becc --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/package.json b/package.json index e77295b..3fdc96d 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,18 @@ { "name": "public-goods-news", - "version": "0.0.1", - "homepage": "/", + "private": true, + "type": "module", + "packageManager": "bun@1.0.27", "scripts": { - "build": "bun build ./src/index.ts --outdir=dist", - "start": "bun run dist/index.js", - "dev": "bun run --watch src/index.ts", - "test": "bun test", - "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", - "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "dev": "bunx turbo run dev", + "build": "bunx turbo run build", + "lint": "bunx turbo run lint" }, + "workspaces": [ + "frontend", + "backend" + ], "devDependencies": { - "@types/express": "^4.17.17", - "@types/jest": "^29.5.11", - "@types/node": "^20.10.6", - "@types/ora": "^3.2.0", - "jest": "^29.7.0", - "prettier": "^3.3.3", - "ts-jest": "^29.1.1", - "ts-node": "^10.9.1", - "typescript": "^5.3.3" - }, - "dependencies": { - "agent-twitter-client": "^0.0.16", - "dotenv": "^16.0.3", - "express": "^4.18.2", - "near-api-js": "^2.1.4", - "ora": "^8.1.1", - "winston": "^3.17.0", - "winston-console-format": "^1.0.8" + "turbo": "latest" } } diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..e818cf5 --- /dev/null +++ b/turbo.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env"], + "globalEnv": ["NODE_ENV"], + "tasks": { + "dev": { + "cache": false, + "persistent": true + }, + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "lint": { + "outputs": [] + } + } +} From 5ae9dd15db873f352de7fdf9ef6d4652244c370b Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 14:28:30 -0600 Subject: [PATCH 02/13] adds readmes --- README.md | 257 +++++++++++++++++++++ backend/Dockerfile | 27 +++ backend/README.md | 185 ++++++--------- backend/src/services/db/index.ts | 27 ++- backend/src/services/twitter/client.ts | 4 +- backend/src/types/twitter.ts | 6 +- fly.toml | 26 +++ frontend/README.md | 156 +++++++++---- frontend/src/components/SubmissionList.tsx | 23 +- frontend/src/types/twitter.ts | 20 +- 10 files changed, 547 insertions(+), 184 deletions(-) create mode 100644 README.md create mode 100644 backend/Dockerfile create mode 100644 fly.toml diff --git a/README.md b/README.md new file mode 100644 index 0000000..73d253c --- /dev/null +++ b/README.md @@ -0,0 +1,257 @@ + + + + + +
+ +

Public Goods News Curation

+ +

+ Bot to curate and to streamline public goods news +

+ +
+ +
+ Table of Contents + +- [Project Structure](#project-structure) + - [Monorepo Overview](#monorepo-overview) + - [Key Components](#key-components) +- [Getting Started](#getting-started) + - [Installing dependencies](#installing-dependencies) + - [Environment Setup](#environment-setup) + - [Running the app](#running-the-app) + - [Building for production](#building-for-production) + - [Running tests](#running-tests) +- [Configuration](#configuration) + - [Twitter Setup](#twitter-setup) + - [Admin Configuration](#admin-configuration) + - [NEAR Network Setup](#near-network-setup) +- [Bot Functionality](#bot-functionality) + - [Submission Process](#submission-process) + - [Moderation System](#moderation-system) + - [Rate Limiting](#rate-limiting) +- [Customization](#customization) + - [Frontend Customization](#frontend-customization) + - [Backend Customization](#backend-customization) +- [Contributing](#contributing) + +
+ +## Project Structure + +### Monorepo Overview + +This project uses a monorepo structure managed with [Turborepo](https://turbo.build/repo) for efficient build orchestration: + +``` +public-goods-news/ +โ”œโ”€โ”€ frontend/ # React frontend application +โ”œโ”€โ”€ backend/ # Bun-powered backend service +โ”œโ”€โ”€ package.json # Root package.json for shared dependencies +โ””โ”€โ”€ turbo.json # Turborepo configuration +``` + +### Key Components + +- **Frontend** ([Documentation](./frontend/README.md)) + - React-based web interface + - Built with Vite and Tailwind CSS + - Handles user interactions and submissions + +- **Backend** ([Documentation](./backend/README.md)) + - Bun runtime for high performance + - NEAR blockchain integration + - Twitter bot functionality + - API endpoints for frontend + +## Getting Started + +### Installing dependencies + +The monorepo uses Bun for package management. Install all dependencies with: + +```bash +bun install +``` + +This will install dependencies for both frontend and backend packages. + +### Environment Setup + +Copy the environment template and configure your credentials: + +```bash +cp .env.example .env +``` + +Required environment variables: + +```env +# Twitter API Credentials +TWITTER_USERNAME=your_twitter_username +TWITTER_PASSWORD=your_twitter_password +TWITTER_EMAIL=your_twitter_email + +# NEAR Configuration +NEAR_NETWORK_ID=testnet +NEAR_LIST_CONTRACT=your_list_contract_name +NEAR_SIGNER_ACCOUNT=your_signer_account +NEAR_SIGNER_PRIVATE_KEY=your_signer_private_key +``` + +### Running the app + +Start both frontend and backend development servers: + +```bash +bun run dev +``` + +This will launch: +- Frontend at http://localhost:5173 +- Backend at http://localhost:3000 + +### Building for production + +Build all packages: + +```bash +bun run build +``` + +### Running tests + +```bash +bun run test +``` + +See the full [testing guide](./playwright-tests/README.md). + +## Configuration + +### Twitter Setup + +The bot requires a Twitter account to function. Configure the following in your `.env` file: + +```env +TWITTER_USERNAME=your_twitter_username +TWITTER_PASSWORD=your_twitter_password +TWITTER_EMAIL=your_twitter_email +``` + +It will use these credentials to login and cache cookies via [agent-twitter-client](https://github.com/ai16z/agent-twitter-client). + +### Admin Configuration + +Admins are Twitter accounts that have moderation privileges. Configure admin accounts in `backend/src/config/admins.ts`: + +```typescript +export const ADMIN_ACCOUNTS: string[] = [ + "admin_handle_1", + "admin_handle_2" + // Add admin Twitter handles here (without @) +] +``` + +Admin accounts are automatically tagged in submission acknowledgements and can: + +- Approve submissions using the `#approve` hashtag +- Reject submissions using the `#reject` hashtag + +Only the first moderation will be recorded. + +### NEAR Network Setup + +Configure NEAR network settings in your `.env` file: + +```env +NEAR_NETWORK_ID=testnet +NEAR_CONTRACT_NAME=your_contract_name +``` + +## Bot Functionality + +### Submission Process + +1. **Submit News**: Users can submit news by mentioning the bot with `!submit` in their tweet +2. **Acknowledgment**: The bot responds with a confirmation tweet, tagging the admins for review +3. **Moderation**: Admins will reply to the bot's acknowledgement with either #approve or #reject +4. **Notification**: Users receive a tweet notification about their submission's status + +### Moderation System + +1. **Queue**: All submissions enter a moderation queue +2. **Admin Review**: Admins can review submissions by replying to the bot's acknowledgment tweet +3. **Actions**: + - Approve: Reply with `#approve` hashtag + - Reject: Reply with `#reject` hashtag +4. **Outcome**: Users receive a notification tweet about the moderation decision + +### Rate Limiting + +To maintain quality: + +- Users are limited to 10 submissions per day +- Rate limits reset daily +- Exceeding the limit results in a notification tweet + +## Customization + +### Frontend Customization + +The frontend can be customized in several ways: + +1. **Styling** + - Modify `frontend/tailwind.config.js` for theme customization + - Update global styles in `frontend/src/index.css` + - Component-specific styles in respective component files + +2. **Components** + - Add new components in `frontend/src/components/` + - Modify existing components for different layouts or functionality + +3. **Configuration** + - Update API endpoints in environment variables + - Modify build settings in `vite.config.ts` + +See the [Frontend README](./frontend/README.md) for detailed customization options. + +### Backend Customization + +The backend service can be extended and customized: + +1. **Services** + - Add new services in `backend/src/services/` + - Modify existing services for different functionality + - Extend API endpoints as needed + +2. **Configuration** + - Update environment variables for different integrations + - Modify admin settings in `src/config/` + - Adjust rate limits and other constraints + +3. **Integration** + - Add new blockchain integrations + - Extend social media support + - Implement additional APIs + +See the [Backend README](./backend/README.md) for detailed customization options. + +## Contributing + +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +If you're interested in contributing to this project, please read the [contribution guide](./CONTRIBUTING). + +
+ +Near Builders + +
diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..35ddc55 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,27 @@ +FROM oven/bun + +WORKDIR /app + +# Create directories for mounts +RUN mkdir -p /data +RUN mkdir -p /app/.cache +RUN chown bun:bun /data /app/.cache + +# Copy package files +COPY --chown=bun:bun package.json bun.lockb ./ + +# Install dependencies +RUN bun install + +# Copy the rest of the application +COPY --chown=bun:bun . . + +# Set environment variables +ENV DATABASE_URL="file:/data/sqlite.db" +ENV CACHE_DIR="/app/.cache" + +# Expose the port +EXPOSE 8080 + +# Start the application +CMD ["bun", "run", "src/index.ts"] diff --git a/backend/README.md b/backend/README.md index e7a3566..fee4165 100644 --- a/backend/README.md +++ b/backend/README.md @@ -5,10 +5,10 @@
-

Public Goods News Curation

+

Public Goods News Backend

- Bot to curate and to streamline public goods news + TypeScript-based backend service for the Public Goods News Curation platform

@@ -16,150 +16,107 @@
Table of Contents -- [Getting Started](#getting-started) - - [Installing dependencies](#installing-dependencies) - - [Environment Setup](#environment-setup) - - [Running the app](#running-the-app) - - [Building for production](#building-for-production) - - [Running tests](#running-tests) -- [Configuration](#configuration) - - [Twitter Setup](#twitter-setup) - - [Admin Configuration](#admin-configuration) - - [NEAR Network Setup](#near-network-setup) -- [Bot Functionality](#bot-functionality) - - [Submission Process](#submission-process) - - [Moderation System](#moderation-system) - - [Rate Limiting](#rate-limiting) -- [Contributing](#contributing) +- [Architecture Overview](#architecture-overview) + - [Tech Stack](#tech-stack) + - [Service Architecture](#service-architecture) +- [Key Components](#key-components) + - [Database Service](#database-service) + - [NEAR Integration](#near-integration) + - [Twitter Service](#twitter-service) +- [Development](#development) + - [Prerequisites](#prerequisites) + - [Local Setup](#local-setup) +- [API Documentation](#api-documentation)
-## Getting Started +## Architecture Overview -### Installing dependencies +### Tech Stack -```bash -bun install -``` +The backend is built with modern technologies chosen for their performance, developer experience, and ecosystem: -### Environment Setup +- **Runtime**: [Bun](https://bun.sh) + - Chosen for its exceptional performance and built-in TypeScript support + - Provides native testing capabilities and package management + - Offers excellent developer experience with fast startup times -Copy the environment template and configure your credentials: +- **Language**: TypeScript + - Ensures type safety and better developer experience + - Enables better code organization and maintainability + - Provides excellent IDE support and code navigation -```bash -cp .env.example .env -``` - -Required environment variables: +### Service Architecture -```env -# Twitter API Credentials -TWITTER_USERNAME=your_twitter_username -TWITTER_PASSWORD=your_twitter_password -TWITTER_EMAIL=your_twitter_email +The backend follows a modular service-based architecture: -# NEAR Configuration -NEAR_NETWORK_ID=testnet -NEAR_LIST_CONTRACT=your_list_contract_name -NEAR_SIGNER_ACCOUNT=your_signer_account -NEAR_SIGNER_PRIVATE_KEY=your_signer_private_key ``` - -### Running the app - -First, run the development server: - -```bash -bun run dev +src/ +โ”œโ”€โ”€ config/ # Configuration management +โ”œโ”€โ”€ services/ # Core service implementations +โ”œโ”€โ”€ types/ # TypeScript type definitions +โ””โ”€โ”€ utils/ # Shared utilities ``` -### Building for production +## Key Components -```bash -bun run build -``` +### Database Service -### Running tests +Located in `src/services/db`, handles: +- Data persistence +- Caching layer +- Query optimization -```bash -bun run test -``` +### NEAR Integration -See the full [testing guide](./playwright-tests/README.md). +The NEAR blockchain integration (`src/services/near`) provides: +- Contract interaction +- Transaction management +- Network configuration -## Configuration +### Twitter Service -### Twitter Setup +Twitter integration (`src/services/twitter`) manages: +- Authentication +- Tweet interactions +- Rate limiting +- User management -The bot requires a Twitter account to function. Configure the following in your `.env` file: - -```env -TWITTER_USERNAME=your_twitter_username -TWITTER_PASSWORD=your_twitter_password -TWITTER_EMAIL=your_twitter_email -``` +## Development -It will use these credentials to login and cache cookies via [agent-twitter-client](https://github.com/ai16z/agent-twitter-client). +### Prerequisites -### Admin Configuration +- Bun runtime installed +- Node.js 18+ (for some dev tools) +- Twitter API credentials +- NEAR testnet account -Admins are Twitter accounts that have moderation privileges. Configure admin accounts in `src/config/admins.ts`: +### Local Setup -```typescript -export const ADMIN_ACCOUNTS: string[] = [ - "admin_handle_1", - "admin_handle_2" - // Add admin Twitter handles here (without @) -] +1. Install dependencies: +```bash +bun install ``` -Admin accounts are automatically tagged in submission acknolwedgements and can: - -- Approve submissions using the `#approve` hashtag -- Reject submissions using the `#reject` hashtag - -Only the first moderation will be recorded. - -### NEAR Network Setup - -Configure NEAR network settings in your `.env` file: - -```env -NEAR_NETWORK_ID=testnet -NEAR_CONTRACT_NAME=your_contract_name +2. Configure environment: +```bash +cp .env.example .env ``` -## Bot Functionality - -### Submission Process - -1. **Submit News**: Users can submit news by mentioning the bot with `!submit` in their tweet -2. **Acknowledgment**: The bot responds with a confirmation tweet, tagging the admins for review -3. **Moderation**: Admins will reply to the bot's acknowledgement with either #approve or #reject -4. **Notification**: Users receive a tweet notification about their submission's status - -### Moderation System - -1. **Queue**: All submissions enter a moderation queue -2. **Admin Review**: Admins can review submissions by replying to the bot's acknowledgment tweet -3. **Actions**: - - Approve: Reply with `#approve` hashtag - - Reject: Reply with `#reject` hashtag -4. **Outcome**: Users receive a notification tweet about the moderation decision - -### Rate Limiting - -To maintain quality: +3. Start development server: +```bash +bun run dev +``` -- Users are limited to 10 submissions per day -- Rate limits reset daily -- Exceeding the limit results in a notification tweet +## API Documentation -## Contributing +The backend exposes several endpoints for frontend interaction: -Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. +- `POST /submit`: Submit new content +- `GET /submissions`: Retrieve submission list +- `POST /moderate`: Handle moderation actions -If you're interested in contributing to this project, please read the [contribution guide](./CONTRIBUTING). +See the [Frontend README](../frontend/README.md) for integration details.
diff --git a/backend/src/services/db/index.ts b/backend/src/services/db/index.ts index 5d1af48..f5693d7 100644 --- a/backend/src/services/db/index.ts +++ b/backend/src/services/db/index.ts @@ -27,6 +27,7 @@ export class DatabaseService { CREATE TABLE IF NOT EXISTS submissions ( tweet_id TEXT PRIMARY KEY, user_id TEXT NOT NULL, + username TEXT NOT NULL, content TEXT NOT NULL, hashtags TEXT NOT NULL, category TEXT, @@ -34,7 +35,7 @@ export class DatabaseService { status TEXT NOT NULL DEFAULT 'pending', acknowledgment_tweet_id TEXT, moderation_response_tweet_id TEXT, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP + created_at TEXT NOT NULL ) `); @@ -79,24 +80,34 @@ export class DatabaseService { CREATE INDEX IF NOT EXISTS idx_acknowledgment_tweet_id ON submissions(acknowledgment_tweet_id) `); + + // Add new columns if they don't exist + try { + this.db.run(`ALTER TABLE submissions ADD COLUMN username TEXT NOT NULL DEFAULT ''`); + } catch (e) { + // Column might already exist + } } saveSubmission(submission: TwitterSubmission): void { const stmt = this.db.prepare(` INSERT INTO submissions ( - tweet_id, user_id, content, hashtags, category, description, status, acknowledgment_tweet_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + tweet_id, user_id, username, content, hashtags, category, description, status, + acknowledgment_tweet_id, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run( submission.tweetId, submission.userId, + submission.username, submission.content, JSON.stringify(submission.hashtags), submission.category || null, submission.description || null, submission.status, - submission.acknowledgmentTweetId || null + submission.acknowledgmentTweetId || null, + submission.createdAt ); } @@ -145,6 +156,7 @@ export class DatabaseService { return { tweetId: submission.tweet_id, userId: submission.user_id, + username: submission.username, content: submission.content, hashtags: JSON.parse(submission.hashtags), category: submission.category, @@ -152,6 +164,7 @@ export class DatabaseService { status: submission.status, acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, + createdAt: submission.created_at, moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, @@ -182,6 +195,7 @@ export class DatabaseService { return { tweetId: submission.tweet_id, userId: submission.user_id, + username: submission.username, content: submission.content, hashtags: JSON.parse(submission.hashtags), category: submission.category, @@ -189,6 +203,7 @@ export class DatabaseService { status: submission.status, acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, + createdAt: submission.created_at, moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, @@ -216,6 +231,7 @@ export class DatabaseService { return submissions.map(submission => ({ tweetId: submission.tweet_id, userId: submission.user_id, + username: submission.username, content: submission.content, hashtags: JSON.parse(submission.hashtags), category: submission.category, @@ -223,6 +239,7 @@ export class DatabaseService { status: submission.status, acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, + createdAt: submission.created_at, moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, @@ -251,6 +268,7 @@ export class DatabaseService { return submissions.map(submission => ({ tweetId: submission.tweet_id, userId: submission.user_id, + username: submission.username, content: submission.content, hashtags: JSON.parse(submission.hashtags), category: submission.category, @@ -258,6 +276,7 @@ export class DatabaseService { status: submission.status, acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, + createdAt: submission.created_at, moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, diff --git a/backend/src/services/twitter/client.ts b/backend/src/services/twitter/client.ts index f69422f..9d44d11 100644 --- a/backend/src/services/twitter/client.ts +++ b/backend/src/services/twitter/client.ts @@ -239,11 +239,13 @@ export class TwitterService { // Create submission using the original tweet's content const submission: TwitterSubmission = { tweetId: originalTweet.id!, // The tweet being submitted - userId: userId, // The user who submitted it + userId: originalTweet.userId!, + username: originalTweet.username!, content: originalTweet.text || "", hashtags: originalTweet.hashtags || [], status: "pending", moderationHistory: [], + createdAt: originalTweet.timeParsed?.toISOString() || new Date().toISOString(), }; // Save submission to database diff --git a/backend/src/types/twitter.ts b/backend/src/types/twitter.ts index ec93ded..d6735d7 100644 --- a/backend/src/types/twitter.ts +++ b/backend/src/types/twitter.ts @@ -1,6 +1,7 @@ export interface TwitterSubmission { tweetId: string; userId: string; + username: string; content: string; hashtags: Array; category?: string; @@ -9,6 +10,7 @@ export interface TwitterSubmission { moderationHistory: Moderation[]; acknowledgmentTweetId?: string; moderationResponseTweetId?: string; + createdAt: string; } export interface Moderation { @@ -22,8 +24,4 @@ export interface TwitterConfig { username: string; password: string; email: string; - apiKey: string; - apiSecret: string; - accessToken: string; - accessTokenSecret: string; } diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..a01de92 --- /dev/null +++ b/fly.toml @@ -0,0 +1,26 @@ +app = "public-goods-news" +primary_region = "lax" + +[build] + dockerfile = "backend/Dockerfile" + +[env] + PORT = "8080" + +[http_service] + internal_port = 8080 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ["app"] + +[[mounts]] + source = "sqlite" + destination = "/data" + initial_size = "1GB" + +[[mounts]] + source = "cache" + destination = "/app/.cache" + initial_size = "1GB" diff --git a/frontend/README.md b/frontend/README.md index 74872fd..af8c3e6 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,50 +1,126 @@ -# React + TypeScript + Vite + + + + -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +
-Currently, two official plugins are available: +

Public Goods News Frontend

-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +

+ React-based frontend application for the Public Goods News Curation platform +

-## Expanding the ESLint configuration +
-If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +
+ Table of Contents -- Configure the top-level `parserOptions` property like this: +- [Architecture Overview](#architecture-overview) + - [Tech Stack](#tech-stack) + - [Application Structure](#application-structure) +- [Key Features](#key-features) + - [Submission Interface](#submission-interface) + - [Real-time Updates](#real-time-updates) + - [Responsive Design](#responsive-design) +- [Development](#development) + - [Prerequisites](#prerequisites) + - [Local Setup](#local-setup) +- [Backend Integration](#backend-integration) -```js -export default tseslint.config({ - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) +
+ +## Architecture Overview + +### Tech Stack + +The frontend leverages modern web technologies for optimal performance and developer experience: + +- **Framework**: [React](https://reactjs.org) + TypeScript + - Component-based architecture + - Strong type safety + - Excellent ecosystem support + +- **Build Tool**: [Vite](https://vitejs.dev) + - Lightning-fast development server + - Optimized production builds + - Modern development experience + +- **Styling**: [Tailwind CSS](https://tailwindcss.com) + - Utility-first CSS framework + - Highly customizable + - Zero runtime overhead + +### Application Structure + +``` +src/ +โ”œโ”€โ”€ assets/ # Static assets +โ”œโ”€โ”€ components/ # React components +โ”œโ”€โ”€ types/ # TypeScript definitions +โ””โ”€โ”€ App.tsx # Root component +``` + +## Key Features + +### Submission Interface + +The submission system provides: +- Intuitive submission form +- Real-time validation +- Status tracking +- Moderation feedback + +### Real-time Updates + +- Live submission status updates +- Dynamic content loading +- Optimistic UI updates + +### Responsive Design + +- Mobile-first approach +- Adaptive layouts +- Cross-browser compatibility + +## Development + +### Prerequisites + +- Node.js 18+ +- Bun (recommended) or npm +- Backend service running + +### Local Setup + +1. Install dependencies: +```bash +bun install ``` -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` -- Optionally add `...tseslint.configs.stylisticTypeChecked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: - -```js -// eslint.config.js -import react from 'eslint-plugin-react' - -export default tseslint.config({ - // Set the react version - settings: { react: { version: '18.3' } }, - plugins: { - // Add the react plugin - react, - }, - rules: { - // other rules... - // Enable its recommended rules - ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, - }, -}) +2. Start development server: +```bash +bun run dev ``` + +The app will be available at `http://localhost:5173` + +## Backend Integration + +The frontend communicates with the [backend service](../backend/README.md) through a RESTful API: + +- Submission handling via `/submit` endpoint +- Content retrieval through `/submissions` +- Real-time updates using polling (future: WebSocket support) + +See the [Backend README](../backend/README.md) for detailed API documentation. + +
diff --git a/frontend/src/components/SubmissionList.tsx b/frontend/src/components/SubmissionList.tsx index ee878a0..a62c3f0 100644 --- a/frontend/src/components/SubmissionList.tsx +++ b/frontend/src/components/SubmissionList.tsx @@ -37,6 +37,14 @@ const SubmissionList = () => { return () => clearInterval(interval); }, [filter]); + const getTweetUrl = (tweetId: string, username: string) => { + return `https://x.com/${username}/status/${tweetId}`; + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleString(); + }; + if (loading) { return (
@@ -87,8 +95,19 @@ const SubmissionList = () => { className="bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow" >
-
-

Tweet ID: {submission.tweetId}

+
+
+ + @{submission.username} + + ยท + {formatDate(submission.createdAt)} +

{submission.content}

{submission.hashtags.map((tag) => ( diff --git a/frontend/src/types/twitter.ts b/frontend/src/types/twitter.ts index 4cc9b4a..59abb36 100644 --- a/frontend/src/types/twitter.ts +++ b/frontend/src/types/twitter.ts @@ -1,19 +1 @@ -export interface TwitterSubmission { - tweetId: string; - userId: string; - content: string; - hashtags: Array; - category?: string; - description?: string; - status: "pending" | "approved" | "rejected"; - moderationHistory: Moderation[]; - acknowledgmentTweetId?: string; - moderationResponseTweetId?: string; -} - -export interface Moderation { - adminId: string; - action: "approve" | "reject"; - timestamp: Date; - tweetId: string; -} +export * from '../../../backend/src/types/twitter'; From 58f8f165014c941ec2f62759af65a3ac9f37d598 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 14:32:09 -0600 Subject: [PATCH 03/13] fly documentation --- README.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++---- package.json | 5 ++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 73d253c..f2b62ac 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ - [Environment Setup](#environment-setup) - [Running the app](#running-the-app) - [Building for production](#building-for-production) + - [Deploying to Fly.io](#deploying-to-flyio) - [Running tests](#running-tests) - [Configuration](#configuration) - [Twitter Setup](#twitter-setup) @@ -46,12 +47,12 @@ This project uses a monorepo structure managed with [Turborepo](https://turbo.build/repo) for efficient build orchestration: -``` +```bash public-goods-news/ โ”œโ”€โ”€ frontend/ # React frontend application -โ”œโ”€โ”€ backend/ # Bun-powered backend service -โ”œโ”€โ”€ package.json # Root package.json for shared dependencies -โ””โ”€โ”€ turbo.json # Turborepo configuration +โ”œโ”€โ”€ backend/ # Bun-powered backend service +โ”œโ”€โ”€ package.json # Root package.json for shared dependencies +โ””โ”€โ”€ turbo.json # Turborepo configuration ``` ### Key Components @@ -111,6 +112,7 @@ bun run dev ``` This will launch: + - Frontend at http://localhost:5173 - Backend at http://localhost:3000 @@ -122,6 +124,49 @@ Build all packages: bun run build ``` +### Deploying to Fly.io + +The backend service can be deployed to Fly.io with SQLite support. First, install the Fly CLI: + +```bash +# macOS +brew install flyctl + +# Windows +powershell -Command "iwr https://fly.io/install.ps1 -useb | iex" + +# Linux +curl -L https://fly.io/install.sh | sh +``` + +Then sign up and authenticate: + +```bash +fly auth signup +# or +fly auth login +``` + +Deploy the application using the provided npm scripts: + +```bash +# Initialize Fly.io app +bun run deploy:init + +# Create persistent volumes for SQLite and cache +bun run deploy:volumes + +# Deploy the application +bun run deploy +``` + +The deployment configuration includes: + +- Persistent storage for SQLite database +- Cache directory support +- Auto-scaling configuration +- HTTPS enabled by default + ### Running tests ```bash diff --git a/package.json b/package.json index 3fdc96d..60d615f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "scripts": { "dev": "bunx turbo run dev", "build": "bunx turbo run build", - "lint": "bunx turbo run lint" + "lint": "bunx turbo run lint", + "deploy:init": "fly launch", + "deploy:volumes": "fly volumes create sqlite --size 1 --region lax && fly volumes create cache --size 1 --region lax", + "deploy": "fly deploy" }, "workspaces": [ "frontend", From be3495f517b1475cb0db0253e87ac19501a2a4e4 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 14:38:13 -0600 Subject: [PATCH 04/13] serve front end --- backend/CONTRIBUTING.md => CONTRIBUTING.md | 0 Dockerfile | 33 ++++++++++++++++++++++ backend/Dockerfile | 27 ------------------ backend/README.md | 6 ++++ backend/src/index.ts | 4 +-- fly.toml | 6 ++-- frontend/README.md | 5 +++- frontend/vite.config.ts | 9 ++++++ package.json | 1 + 9 files changed, 58 insertions(+), 33 deletions(-) rename backend/CONTRIBUTING.md => CONTRIBUTING.md (100%) create mode 100644 Dockerfile delete mode 100644 backend/Dockerfile diff --git a/backend/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from backend/CONTRIBUTING.md rename to CONTRIBUTING.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5e169c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM oven/bun + +WORKDIR /app + +# Create directories for mounts +RUN mkdir -p /data +RUN mkdir -p /app/.cache +RUN chown bun:bun /data /app/.cache + +# Copy package files for all workspaces +COPY --chown=bun:bun package.json bun.lockb turbo.json ./ +COPY --chown=bun:bun frontend/package.json ./frontend/ +COPY --chown=bun:bun backend/package.json ./backend/ + +# Install dependencies +RUN bun install + +# Copy the rest of the application +COPY --chown=bun:bun . . + +# Build both frontend and backend +RUN bun run build + +# Set environment variables +ENV DATABASE_URL="file:/data/sqlite.db" +ENV CACHE_DIR="/app/.cache" +ENV NODE_ENV="production" + +# Expose the port +EXPOSE 3000 + +# Start the application using the production start script +CMD ["bun", "run", "start"] diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 35ddc55..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM oven/bun - -WORKDIR /app - -# Create directories for mounts -RUN mkdir -p /data -RUN mkdir -p /app/.cache -RUN chown bun:bun /data /app/.cache - -# Copy package files -COPY --chown=bun:bun package.json bun.lockb ./ - -# Install dependencies -RUN bun install - -# Copy the rest of the application -COPY --chown=bun:bun . . - -# Set environment variables -ENV DATABASE_URL="file:/data/sqlite.db" -ENV CACHE_DIR="/app/.cache" - -# Expose the port -EXPOSE 8080 - -# Start the application -CMD ["bun", "run", "src/index.ts"] diff --git a/backend/README.md b/backend/README.md index fee4165..2c12429 100644 --- a/backend/README.md +++ b/backend/README.md @@ -63,6 +63,7 @@ src/ ### Database Service Located in `src/services/db`, handles: + - Data persistence - Caching layer - Query optimization @@ -70,6 +71,7 @@ Located in `src/services/db`, handles: ### NEAR Integration The NEAR blockchain integration (`src/services/near`) provides: + - Contract interaction - Transaction management - Network configuration @@ -77,6 +79,7 @@ The NEAR blockchain integration (`src/services/near`) provides: ### Twitter Service Twitter integration (`src/services/twitter`) manages: + - Authentication - Tweet interactions - Rate limiting @@ -94,16 +97,19 @@ Twitter integration (`src/services/twitter`) manages: ### Local Setup 1. Install dependencies: + ```bash bun install ``` 2. Configure environment: + ```bash cp .env.example .env ``` 3. Start development server: + ```bash bun run dev ``` diff --git a/backend/src/index.ts b/backend/src/index.ts index 99582f7..a391909 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -24,7 +24,7 @@ app.use(express.json()); // Serve static frontend files in production if (process.env.NODE_ENV === 'production') { - app.use(express.static(path.join(__dirname, '../frontend/dist'))); + app.use(express.static(path.join(__dirname, '../../frontend/dist'))); } // API Routes @@ -56,7 +56,7 @@ app.get('/api/submissions/:tweetId', (req, res) => { // Serve frontend for all other routes in production if (process.env.NODE_ENV === 'production') { app.get('*', (req, res) => { - res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); + res.sendFile(path.join(__dirname, '../../frontend/dist/index.html')); }); } diff --git a/fly.toml b/fly.toml index a01de92..cb5432b 100644 --- a/fly.toml +++ b/fly.toml @@ -2,13 +2,13 @@ app = "public-goods-news" primary_region = "lax" [build] - dockerfile = "backend/Dockerfile" + dockerfile = "Dockerfile" [env] - PORT = "8080" + PORT = "3000" [http_service] - internal_port = 8080 + internal_port = 3000 force_https = true auto_stop_machines = true auto_start_machines = true diff --git a/frontend/README.md b/frontend/README.md index af8c3e6..ef274e2 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -53,7 +53,7 @@ The frontend leverages modern web technologies for optimal performance and devel ### Application Structure -``` +```bash src/ โ”œโ”€โ”€ assets/ # Static assets โ”œโ”€โ”€ components/ # React components @@ -66,6 +66,7 @@ src/ ### Submission Interface The submission system provides: + - Intuitive submission form - Real-time validation - Status tracking @@ -94,11 +95,13 @@ The submission system provides: ### Local Setup 1. Install dependencies: + ```bash bun install ``` 2. Start development server: + ```bash bun run dev ``` diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 8b0f57b..4172b9c 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -4,4 +4,13 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + server: { + proxy: { + '/api': 'http://localhost:3000' + } + }, + build: { + outDir: 'dist', + emptyOutDir: true + } }) diff --git a/package.json b/package.json index 60d615f..c2b150a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "dev": "bunx turbo run dev", "build": "bunx turbo run build", + "start": "NODE_ENV=production bun run backend/dist/index.js", "lint": "bunx turbo run lint", "deploy:init": "fly launch", "deploy:volumes": "fly volumes create sqlite --size 1 --region lax && fly volumes create cache --size 1 --region lax", From 334f53ee4d865ae6c844efc525a60449cdcf496d Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 14:44:20 -0600 Subject: [PATCH 05/13] ts errors --- backend/package.json | 6 +++--- backend/tsconfig.json | 11 ++++++----- bun.lockb | Bin 279779 -> 279021 bytes frontend/.gitignore | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/backend/package.json b/backend/package.json index 24185a5..814a7e2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,11 +2,11 @@ "name": "backend", "version": "0.0.1", "packageManager": "bun@1.0.27", + "type": "module", "scripts": { - "build": "bun build ./src/index.ts --outdir=dist", + "build": "bun build ./src/index.ts --target=bun --outdir=dist --format=esm", "start": "bun run dist/index.js", "dev": "bun run --watch src/index.ts", - "test": "bun test", "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" }, @@ -27,9 +27,9 @@ "@types/jest": "^29.5.11", "@types/node": "^20.10.6", "@types/ora": "^3.2.0", + "bun-types": "^1.1.40", "jest": "^29.7.0", "prettier": "^3.3.3", - "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "typescript": "^5.3.3" }, diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 1d1efe1..2e6c2a8 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "lib": ["es2020"], + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, @@ -14,8 +14,9 @@ "moduleResolution": "node", "baseUrl": ".", "paths": { - "@/*": ["src/*"] - } + "*": ["src/*"] + }, + "types": ["bun-types"] }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "tests"] diff --git a/bun.lockb b/bun.lockb index 7225c1446deaadff55a43c797c585eb70e329c47..60cf15076c3f3a9e2a57735489f00108774225a9 100755 GIT binary patch delta 61770 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�jXw7SUk8qRg0!QI$+U=_9Jg)1A%Dx}Z%vJ>F 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 B%klsK diff --git a/frontend/.gitignore b/frontend/.gitignore index a547bf3..6cedcb3 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -7,8 +7,8 @@ yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules -dist +/node_modules +/dist dist-ssr *.local From 6d2162ab0354fd115d42ff4a9ab823520bb22ff1 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 14:45:44 -0600 Subject: [PATCH 06/13] dist to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 45a32ef..f0db091 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ # production /build +**/dist # misc .DS_Store @@ -43,4 +44,4 @@ next-env.d.ts .cache .db -.turbo \ No newline at end of file +.turbo From 417675cb2583d905c30e983a51d4029cd1d6185e Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 15:00:59 -0600 Subject: [PATCH 07/13] fly deploy wip --- .dockerignore | 81 ++++++++++++++++++++++++++++++++ Dockerfile | 11 ++--- backend/src/services/db/index.ts | 10 ++-- backend/src/utils/cache.ts | 12 ++--- fly.toml | 35 ++++++++------ 5 files changed, 116 insertions(+), 33 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6be9e1f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,81 @@ +# flyctl launch added from .gitignore +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +**/.pnp.js +**/.yarn/install-state.gz +.unlighthouse + +# testing +coverage + +# next.js +.next +out + +# production +build +**/**/dist + +# misc +**/.DS_Store +**/*.pem + +# debug +**/npm-debug.log* +**/yarn-debug.log* +**/yarn-error.log* + +# local env files +**/.env*.local +**/.env + +# vercel +**/.vercel + +# typescript +**/*.tsbuildinfo +**/next-env.d.ts +test-results +playwright-report +blob-report +playwright/.cache + +**/.cache +**/.db +**/.turbo + +# flyctl launch added from frontend/.gitignore +# Logs +frontend/**/logs +frontend/**/*.log +frontend/**/npm-debug.log* +frontend/**/yarn-debug.log* +frontend/**/yarn-error.log* +frontend/**/pnpm-debug.log* +frontend/**/lerna-debug.log* + +frontend/node_modules +frontend/dist +frontend/**/dist-ssr +frontend/**/*.local + +# Editor directories and files +frontend/**/.vscode/* +!frontend/**/.vscode/extensions.json +frontend/**/.idea +frontend/**/.DS_Store +frontend/**/*.suo +frontend/**/*.ntvs* +frontend/**/*.njsproj +frontend/**/*.sln +frontend/**/*.sw? + +# flyctl launch added from frontend/node_modules/tailwindcss/stubs/.gitignore +!frontend/node_modules/tailwindcss/stubs/**/* + +# flyctl launch added from node_modules/tailwindcss/stubs/.gitignore +!node_modules/tailwindcss/stubs/**/* +fly.toml diff --git a/Dockerfile b/Dockerfile index 5e169c8..2dd60e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,9 @@ FROM oven/bun WORKDIR /app -# Create directories for mounts -RUN mkdir -p /data -RUN mkdir -p /app/.cache -RUN chown bun:bun /data /app/.cache +# Create directory for mount +RUN mkdir -p /.data/db /.data/cache +RUN chown -R bun:bun /.data # Copy package files for all workspaces COPY --chown=bun:bun package.json bun.lockb turbo.json ./ @@ -22,8 +21,8 @@ COPY --chown=bun:bun . . RUN bun run build # Set environment variables -ENV DATABASE_URL="file:/data/sqlite.db" -ENV CACHE_DIR="/app/.cache" +ENV DATABASE_URL="file:/.data/db/sqlite.db" +ENV CACHE_DIR="/.data/cache" ENV NODE_ENV="production" # Expose the port diff --git a/backend/src/services/db/index.ts b/backend/src/services/db/index.ts index f5693d7..56b79c9 100644 --- a/backend/src/services/db/index.ts +++ b/backend/src/services/db/index.ts @@ -1,13 +1,12 @@ import { Database } from "bun:sqlite"; import { TwitterSubmission, Moderation } from "../../types"; import { mkdir } from "node:fs/promises"; -import { join } from "node:path"; +import { join, dirname } from "node:path"; import { existsSync } from "node:fs"; export class DatabaseService { private db: Database; - private static readonly DB_DIR = ".db"; - private static readonly DB_PATH = join(DatabaseService.DB_DIR, "submissions.sqlite"); + private static readonly DB_PATH = process.env.DATABASE_URL?.replace('file:', '') || join('.db', 'submissions.sqlite'); constructor() { this.ensureDbDirectory(); @@ -16,8 +15,9 @@ export class DatabaseService { } private ensureDbDirectory() { - if (!existsSync(DatabaseService.DB_DIR)) { - mkdir(DatabaseService.DB_DIR, { recursive: true }); + const dbDir = dirname(DatabaseService.DB_PATH); + if (!existsSync(dbDir)) { + mkdir(dbDir, { recursive: true }); } } diff --git a/backend/src/utils/cache.ts b/backend/src/utils/cache.ts index 1cef1ee..7956aa0 100644 --- a/backend/src/utils/cache.ts +++ b/backend/src/utils/cache.ts @@ -16,17 +16,15 @@ interface CookieCache { [username: string]: TwitterCookie[]; } -const CACHE_DIR = '.cache'; +const CACHE_DIR = process.env.CACHE_DIR || '.cache'; export async function ensureCacheDirectory() { try { - const cacheDir = path.join(process.cwd(), CACHE_DIR); - try { - await fs.access(cacheDir); + await fs.access(CACHE_DIR); } catch { // Directory doesn't exist, create it - await fs.mkdir(cacheDir, { recursive: true }); + await fs.mkdir(CACHE_DIR, { recursive: true }); logger.info('Created cache directory'); } } catch (error) { @@ -38,7 +36,7 @@ export async function ensureCacheDirectory() { export async function getCachedCookies(username: string): Promise { try { // Try to read cookies from a local cache file - const cookiePath = path.join(process.cwd(), CACHE_DIR, '.twitter-cookies.json'); + const cookiePath = path.join(CACHE_DIR, '.twitter-cookies.json'); const data = await fs.readFile(cookiePath, 'utf-8'); const cache: CookieCache = JSON.parse(data); @@ -55,7 +53,7 @@ export async function getCachedCookies(username: string): Promise Date: Wed, 18 Dec 2024 15:04:58 -0600 Subject: [PATCH 08/13] removes NEAR --- README.md | 17 -------- backend/.env.example | 6 --- backend/README.md | 10 ----- backend/package.json | 1 - backend/src/index.ts | 8 +--- backend/src/services/near/index.ts | 60 ----------------------------- bun.lockb | Bin 279021 -> 263761 bytes 7 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 backend/src/services/near/index.ts diff --git a/README.md b/README.md index f2b62ac..80d88e7 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ - [Configuration](#configuration) - [Twitter Setup](#twitter-setup) - [Admin Configuration](#admin-configuration) - - [NEAR Network Setup](#near-network-setup) - [Bot Functionality](#bot-functionality) - [Submission Process](#submission-process) - [Moderation System](#moderation-system) @@ -64,7 +63,6 @@ public-goods-news/ - **Backend** ([Documentation](./backend/README.md)) - Bun runtime for high performance - - NEAR blockchain integration - Twitter bot functionality - API endpoints for frontend @@ -95,12 +93,6 @@ Required environment variables: TWITTER_USERNAME=your_twitter_username TWITTER_PASSWORD=your_twitter_password TWITTER_EMAIL=your_twitter_email - -# NEAR Configuration -NEAR_NETWORK_ID=testnet -NEAR_LIST_CONTRACT=your_list_contract_name -NEAR_SIGNER_ACCOUNT=your_signer_account -NEAR_SIGNER_PRIVATE_KEY=your_signer_private_key ``` ### Running the app @@ -208,15 +200,6 @@ Admin accounts are automatically tagged in submission acknowledgements and can: Only the first moderation will be recorded. -### NEAR Network Setup - -Configure NEAR network settings in your `.env` file: - -```env -NEAR_NETWORK_ID=testnet -NEAR_CONTRACT_NAME=your_contract_name -``` - ## Bot Functionality ### Submission Process diff --git a/backend/.env.example b/backend/.env.example index 97d2b1f..690bbe8 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -3,11 +3,5 @@ TWITTER_USERNAME=your_twitter_username TWITTER_PASSWORD=your_twitter_password TWITTER_EMAIL=your_twitter_email -# NEAR Configuration -NEAR_NETWORK_ID=testnet -NEAR_LIST_CONTRACT=your_list_contract_name -NEAR_SIGNER_ACCOUNT=your_signer_account -NEAR_SIGNER_PRIVATE_KEY=your_signer_private_key - # Environment NODE_ENV=development diff --git a/backend/README.md b/backend/README.md index 2c12429..a021c6d 100644 --- a/backend/README.md +++ b/backend/README.md @@ -21,7 +21,6 @@ - [Service Architecture](#service-architecture) - [Key Components](#key-components) - [Database Service](#database-service) - - [NEAR Integration](#near-integration) - [Twitter Service](#twitter-service) - [Development](#development) - [Prerequisites](#prerequisites) @@ -68,14 +67,6 @@ Located in `src/services/db`, handles: - Caching layer - Query optimization -### NEAR Integration - -The NEAR blockchain integration (`src/services/near`) provides: - -- Contract interaction -- Transaction management -- Network configuration - ### Twitter Service Twitter integration (`src/services/twitter`) manages: @@ -92,7 +83,6 @@ Twitter integration (`src/services/twitter`) manages: - Bun runtime installed - Node.js 18+ (for some dev tools) - Twitter API credentials -- NEAR testnet account ### Local Setup diff --git a/backend/package.json b/backend/package.json index 814a7e2..95070db 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,7 +39,6 @@ "@types/cors": "^2.8.17", "dotenv": "^16.0.3", "express": "^4.18.2", - "near-api-js": "^2.1.4", "ora": "^8.1.1", "winston": "^3.17.0", "winston-console-format": "^1.0.8" diff --git a/backend/src/index.ts b/backend/src/index.ts index a391909..060b826 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,7 +3,6 @@ import express from "express"; import cors from "cors"; import path from "path"; import { TwitterService } from "./services/twitter/client"; -import { NearService } from "./services/near"; import { db } from "./services/db"; import config from "./config/config"; import { @@ -67,11 +66,6 @@ async function main() { dotenv.config(); succeedSpinner('env', 'Environment variables loaded'); - // Initialize NEAR service - startSpinner('near', 'Initializing NEAR service...'); - const nearService = new NearService(config.near); - succeedSpinner('near', 'NEAR service initialized'); - // Initialize Twitter service startSpinner('twitter-init', 'Initializing Twitter service...'); const twitterService = new TwitterService(config.twitter); @@ -110,7 +104,7 @@ async function main() { } catch (error) { // Handle any initialization errors - ['env', 'near', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { + ['env', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { failSpinner(key, `Failed during ${key}`); }); logger.error('Startup', error); diff --git a/backend/src/services/near/index.ts b/backend/src/services/near/index.ts deleted file mode 100644 index 28e8a01..0000000 --- a/backend/src/services/near/index.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { connect, keyStores, Near } from "near-api-js"; -import type { NearConfig } from "../../types"; - -export class NearService { - private near: Near; - - constructor(config: NearConfig) { - this.near = new Near({ - ...config, - keyStore: new keyStores.InMemoryKeyStore(), - headers: {}, - }); - } - - async getAccount(accountId: string) { - try { - const account = await this.near.account(accountId); - return account; - } catch (error) { - console.error("Error getting NEAR account:", error); - throw error; - } - } - - async viewMethod(contractId: string, method: string, args: object = {}) { - try { - const account = await this.near.account(contractId); - const result = await account.viewFunction({ - contractId, - methodName: method, - args, - }); - return result; - } catch (error) { - console.error("Error calling view method:", error); - throw error; - } - } - - async callMethod( - contractId: string, - method: string, - args: object = {}, - deposit: string = "0", - ) { - try { - const account = await this.near.account(contractId); - const result = await account.functionCall({ - contractId, - methodName: method, - args, - attachedDeposit: deposit, - }); - return result; - } catch (error) { - console.error("Error calling method:", error); - throw error; - } - } -} diff --git a/bun.lockb b/bun.lockb index 60cf15076c3f3a9e2a57735489f00108774225a9..c136ad81eee8da5dc939ede31598354598b9897b 100755 GIT binary patch delta 26326 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 delta 35349 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 Date: Wed, 18 Dec 2024 15:14:51 -0600 Subject: [PATCH 09/13] remove near, fix types --- backend/src/config/config.ts | 17 ++++++----------- backend/src/index.ts | 1 - backend/src/services/twitter/client.ts | 6 +++--- backend/src/types/index.ts | 2 -- backend/src/types/near.ts | 15 --------------- 5 files changed, 9 insertions(+), 32 deletions(-) delete mode 100644 backend/src/types/near.ts diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts index 052802b..6cfb013 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config/config.ts @@ -1,20 +1,15 @@ import { AppConfig } from "../types"; +// Validate required Twitter credentials +if (!process.env.TWITTER_USERNAME || !process.env.TWITTER_PASSWORD || !process.env.TWITTER_EMAIL) { + throw new Error('Missing required Twitter credentials. Please ensure TWITTER_USERNAME, TWITTER_PASSWORD, and TWITTER_EMAIL are set in your environment variables.'); +} + const config: AppConfig = { twitter: { username: process.env.TWITTER_USERNAME!, password: process.env.TWITTER_PASSWORD!, - email: process.env.TWITTER_EMAIL!, - apiKey: process.env.TWITTER_API_KEY || "", - apiSecret: process.env.TWITTER_API_SECRET || "", - accessToken: process.env.TWITTER_ACCESS_TOKEN || "", - accessTokenSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET || "", - }, - near: { - networkId: process.env.NEAR_NETWORK_ID || "testnet", - nodeUrl: process.env.NEAR_NODE_URL || "https://rpc.testnet.near.org", - walletUrl: process.env.NEAR_WALLET_URL || "https://wallet.testnet.near.org", - contractName: process.env.NEAR_CONTRACT_NAME || "dev-1234567890-1234567890", + email: process.env.TWITTER_EMAIL! }, environment: (process.env.NODE_ENV as "development" | "production" | "test") || diff --git a/backend/src/index.ts b/backend/src/index.ts index 060b826..b08eaa4 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -93,7 +93,6 @@ async function main() { }); logger.info('๐Ÿš€ Bot is running and ready for events', { - nearNetwork: config.near.networkId, twitterEnabled: true }); diff --git a/backend/src/services/twitter/client.ts b/backend/src/services/twitter/client.ts index 9d44d11..c402c9f 100644 --- a/backend/src/services/twitter/client.ts +++ b/backend/src/services/twitter/client.ts @@ -1,6 +1,5 @@ import { Scraper, SearchMode, Tweet } from "agent-twitter-client"; import { TwitterSubmission, Moderation, TwitterConfig } from "../../types/twitter"; -import { ADMIN_ACCOUNTS } from "../../config/admins"; import { logger } from "../../utils/logger"; import { db } from "../db"; import { @@ -9,6 +8,7 @@ import { getCachedCookies, cacheCookies, } from "../../utils/cache"; +import { ADMIN_ACCOUNTS } from "config/admins"; export class TwitterService { private client: Scraper; @@ -16,7 +16,7 @@ export class TwitterService { private twitterUsername: string; private config: TwitterConfig; private isInitialized = false; - private checkInterval: NodeJS.Timeout | null = null; + private checkInterval: NodeJS.Timer | null = null; private lastCheckedTweetId: string | null = null; private adminIdCache: Map = new Map(); @@ -338,7 +338,7 @@ export class TwitterService { } private getModerationAction(tweet: Tweet): "approve" | "reject" | null { - const hashtags = tweet.hashtags.map(tag => tag.toLowerCase()); + const hashtags = tweet.hashtags?.map(tag => tag.toLowerCase()) || []; if (hashtags.includes("approve")) return "approve"; if (hashtags.includes("reject")) return "reject"; return null; diff --git a/backend/src/types/index.ts b/backend/src/types/index.ts index 66dce56..38d59cc 100644 --- a/backend/src/types/index.ts +++ b/backend/src/types/index.ts @@ -1,8 +1,6 @@ export * from "./twitter"; -export * from "./near"; export interface AppConfig { twitter: import("./twitter").TwitterConfig; - near: import("./near").NearConfig; environment: "development" | "production" | "test"; } diff --git a/backend/src/types/near.ts b/backend/src/types/near.ts deleted file mode 100644 index e8d05fc..0000000 --- a/backend/src/types/near.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface NewsProposal { - id: string; - submitter: string; - content: string; - category: string; - status: "pending" | "approved" | "rejected"; - tweetId: string; -} - -export interface NearConfig { - networkId: string; - nodeUrl: string; - walletUrl: string; - contractName: string; -} From d3a01525a8c694240d85eea0fa0278ac1573b85a Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 15:24:20 -0600 Subject: [PATCH 10/13] working --- Dockerfile | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2dd60e2..06b1b9c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,42 @@ -FROM oven/bun +FROM oven/bun as deps WORKDIR /app -# Create directory for mount -RUN mkdir -p /.data/db /.data/cache -RUN chown -R bun:bun /.data - # Copy package files for all workspaces -COPY --chown=bun:bun package.json bun.lockb turbo.json ./ -COPY --chown=bun:bun frontend/package.json ./frontend/ -COPY --chown=bun:bun backend/package.json ./backend/ +COPY package.json bun.lockb turbo.json ./ +COPY frontend/package.json ./frontend/ +COPY backend/package.json ./backend/ # Install dependencies RUN bun install -# Copy the rest of the application -COPY --chown=bun:bun . . +# Build stage +FROM oven/bun as builder +WORKDIR /app + +# Copy all files from deps stage including node_modules +COPY --from=deps /app ./ + +# Copy source code +COPY . . # Build both frontend and backend RUN bun run build +# Production stage +FROM oven/bun as production +WORKDIR /app + +# Create directory for mount with correct permissions +RUN mkdir -p /.data/db /.data/cache && \ + chown -R bun:bun /.data + +# Copy only necessary files from builder +COPY --from=builder --chown=bun:bun /app/package.json /app/bun.lockb /app/turbo.json ./ +COPY --from=builder --chown=bun:bun /app/node_modules ./node_modules +COPY --from=builder --chown=bun:bun /app/frontend/dist ./frontend/dist +COPY --from=builder --chown=bun:bun /app/backend/dist ./backend/dist + # Set environment variables ENV DATABASE_URL="file:/.data/db/sqlite.db" ENV CACHE_DIR="/.data/cache" From 8cfdba654f3363eeffba2f3005cf847b53fc49f5 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Wed, 18 Dec 2024 22:07:22 -0600 Subject: [PATCH 11/13] see front end from app --- Dockerfile | 3 +++ frontend/src/App.tsx | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 06b1b9c..cd28374 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,9 @@ RUN bun install FROM oven/bun as builder WORKDIR /app +# Set NODE_ENV for build process +ENV NODE_ENV="production" + # Copy all files from deps stage including node_modules COPY --from=deps /app ./ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4304b65..69a6b35 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,10 +1,6 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import axios from 'axios'; import SubmissionList from './components/SubmissionList'; -// Configure axios base URL for API requests -axios.defaults.baseURL = 'http://localhost:3000'; - function App() { return ( From 43217e77aed48ccb6a22827d4ea258a7d6cee048 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Thu, 19 Dec 2024 09:57:29 -0600 Subject: [PATCH 12/13] ws wip --- backend/src/config/config.ts | 12 +- backend/src/index.ts | 119 +++++++++++--------- backend/src/services/db/index.ts | 20 ++++ backend/src/services/websocket/index.ts | 42 +++++++ frontend/src/App.tsx | 13 ++- frontend/src/components/SubmissionList.tsx | 52 ++++++--- frontend/src/contexts/LiveUpdateContext.tsx | 75 ++++++++++++ frontend/vite.config.ts | 6 +- 8 files changed, 257 insertions(+), 82 deletions(-) create mode 100644 backend/src/services/websocket/index.ts create mode 100644 frontend/src/contexts/LiveUpdateContext.tsx diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts index 6cfb013..4cd0dae 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config/config.ts @@ -1,10 +1,5 @@ import { AppConfig } from "../types"; -// Validate required Twitter credentials -if (!process.env.TWITTER_USERNAME || !process.env.TWITTER_PASSWORD || !process.env.TWITTER_EMAIL) { - throw new Error('Missing required Twitter credentials. Please ensure TWITTER_USERNAME, TWITTER_PASSWORD, and TWITTER_EMAIL are set in your environment variables.'); -} - const config: AppConfig = { twitter: { username: process.env.TWITTER_USERNAME!, @@ -16,4 +11,11 @@ const config: AppConfig = { "development", }; +export function validateEnv() { + // Validate required Twitter credentials + if (!process.env.TWITTER_USERNAME || !process.env.TWITTER_PASSWORD || !process.env.TWITTER_EMAIL) { + throw new Error('Missing required Twitter credentials. Please ensure TWITTER_USERNAME, TWITTER_PASSWORD, and TWITTER_EMAIL are set in your environment variables.'); + } +} + export default config; diff --git a/backend/src/index.ts b/backend/src/index.ts index b08eaa4..c501946 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,10 +1,9 @@ import dotenv from "dotenv"; -import express from "express"; -import cors from "cors"; import path from "path"; import { TwitterService } from "./services/twitter/client"; import { db } from "./services/db"; -import config from "./config/config"; +import { WebSocketService } from "./services/websocket"; +import config, { validateEnv } from "./config/config"; import { logger, startSpinner, @@ -13,57 +12,14 @@ import { cleanup } from "./utils/logger"; -// Initialize Express -const app = express(); -const PORT = process.env.PORT || 3000; - -// Middleware -app.use(cors()); -app.use(express.json()); - -// Serve static frontend files in production -if (process.env.NODE_ENV === 'production') { - app.use(express.static(path.join(__dirname, '../../frontend/dist'))); -} - -// API Routes -app.get('/api/submissions', (req, res) => { - try { - const status = req.query.status as "pending" | "approved" | "rejected"; - const submissions = status ? - db.getSubmissionsByStatus(status) : - db.getAllSubmissions(); - res.json(submissions); - } catch (error) { - res.status(500).json({ error: 'Failed to fetch submissions' }); - } -}); - -app.get('/api/submissions/:tweetId', (req, res) => { - try { - const submission = db.getSubmission(req.params.tweetId); - if (!submission) { - res.status(404).json({ error: 'Submission not found' }); - return; - } - res.json(submission); - } catch (error) { - res.status(500).json({ error: 'Failed to fetch submission' }); - } -}); - -// Serve frontend for all other routes in production -if (process.env.NODE_ENV === 'production') { - app.get('*', (req, res) => { - res.sendFile(path.join(__dirname, '../../frontend/dist/index.html')); - }); -} +const PORT = Number(process.env.PORT) || 3000; async function main() { try { // Load environment variables startSpinner('env', 'Loading environment variables...'); dotenv.config(); + validateEnv(); succeedSpinner('env', 'Environment variables loaded'); // Initialize Twitter service @@ -72,11 +28,65 @@ async function main() { await twitterService.initialize(); succeedSpinner('twitter-init', 'Twitter service initialized'); - // Start Express server - startSpinner('express', 'Starting Express server...'); - app.listen(PORT, () => { - succeedSpinner('express', `Express server running on port ${PORT}`); + // Initialize services + startSpinner('server', 'Starting server...'); + const wsService = new WebSocketService(); + db.setWebSocketService(wsService); + + const server = Bun.serve({ + port: PORT, + async fetch(req) { + const url = new URL(req.url); + + // WebSocket upgrade + if (url.pathname === '/ws') { + if (server.upgrade(req)) { + return; + } + return new Response('WebSocket upgrade failed', { status: 500 }); + } + + // API Routes + if (url.pathname.startsWith('/api')) { + try { + if (url.pathname === '/api/submissions') { + const status = url.searchParams.get('status') as "pending" | "approved" | "rejected" | null; + const submissions = status ? + db.getSubmissionsByStatus(status) : + db.getAllSubmissions(); + return Response.json(submissions); + } + + const match = url.pathname.match(/^\/api\/submissions\/(.+)$/); + if (match) { + const tweetId = match[1]; + const submission = db.getSubmission(tweetId); + if (!submission) { + return Response.json({ error: 'Submission not found' }, { status: 404 }); + } + return Response.json(submission); + } + } catch (error) { + return Response.json({ error: 'Internal server error' }, { status: 500 }); + } + } + + // Serve static frontend files in production + if (process.env.NODE_ENV === 'production') { + const filePath = url.pathname === '/' ? '/index.html' : url.pathname; + const file = Bun.file(path.join(__dirname, '../../frontend/dist', filePath)); + if (await file.exists()) { + return new Response(file); + } + // Fallback to index.html for client-side routing + return new Response(Bun.file(path.join(__dirname, '../../frontend/dist/index.html'))); + } + + return new Response('Not found', { status: 404 }); + }, + websocket: wsService.getWebSocketConfig(), }); + succeedSpinner('server', `Server running on port ${PORT}`); // Handle graceful shutdown process.on("SIGINT", async () => { @@ -93,7 +103,8 @@ async function main() { }); logger.info('๐Ÿš€ Bot is running and ready for events', { - twitterEnabled: true + twitterEnabled: true, + websocketEnabled: true }); // Start checking for mentions @@ -103,7 +114,7 @@ async function main() { } catch (error) { // Handle any initialization errors - ['env', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { + ['env', 'websocket', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { failSpinner(key, `Failed during ${key}`); }); logger.error('Startup', error); diff --git a/backend/src/services/db/index.ts b/backend/src/services/db/index.ts index 56b79c9..62db2ae 100644 --- a/backend/src/services/db/index.ts +++ b/backend/src/services/db/index.ts @@ -3,9 +3,11 @@ import { TwitterSubmission, Moderation } from "../../types"; import { mkdir } from "node:fs/promises"; import { join, dirname } from "node:path"; import { existsSync } from "node:fs"; +import { WebSocketService } from "../websocket"; export class DatabaseService { private db: Database; + private wsService?: WebSocketService; private static readonly DB_PATH = process.env.DATABASE_URL?.replace('file:', '') || join('.db', 'submissions.sqlite'); constructor() { @@ -14,6 +16,16 @@ export class DatabaseService { this.initialize(); } + setWebSocketService(wsService: WebSocketService) { + this.wsService = wsService; + } + + private notifyUpdate() { + if (this.wsService) { + this.wsService.broadcastSubmissions(this.getAllSubmissions()); + } + } + private ensureDbDirectory() { const dbDir = dirname(DatabaseService.DB_PATH); if (!existsSync(dbDir)) { @@ -109,6 +121,8 @@ export class DatabaseService { submission.acknowledgmentTweetId || null, submission.createdAt ); + + this.notifyUpdate(); } saveModerationAction(moderation: Moderation): void { @@ -124,6 +138,8 @@ export class DatabaseService { moderation.action, moderation.timestamp.toISOString() ); + + this.notifyUpdate(); } updateSubmissionStatus(tweetId: string, status: TwitterSubmission['status'], moderationResponseTweetId: string): void { @@ -133,6 +149,8 @@ export class DatabaseService { moderation_response_tweet_id = ? WHERE tweet_id = ? `).run(status, moderationResponseTweetId, tweetId); + + this.notifyUpdate(); } getSubmission(tweetId: string): TwitterSubmission | null { @@ -347,6 +365,8 @@ export class DatabaseService { SET acknowledgment_tweet_id = ? WHERE tweet_id = ? `).run(acknowledgmentTweetId, tweetId); + + this.notifyUpdate(); } } diff --git a/backend/src/services/websocket/index.ts b/backend/src/services/websocket/index.ts new file mode 100644 index 0000000..c46e8d1 --- /dev/null +++ b/backend/src/services/websocket/index.ts @@ -0,0 +1,42 @@ +import { ServerWebSocket, Server } from "bun"; +import { TwitterSubmission } from "../../types"; +import { logger } from "../../utils/logger"; + +export class WebSocketService { + private clients = new Set>(); + + constructor() {} + + getWebSocketConfig() { + return { + open: (ws: ServerWebSocket) => this.handleOpen(ws), + close: (ws: ServerWebSocket) => this.handleClose(ws), + message: (ws: ServerWebSocket, message: string | Buffer) => this.handleMessage(ws, message), + }; + } + + handleOpen(ws: ServerWebSocket) { + this.clients.add(ws); + logger.debug('WebSocket client connected'); + } + + handleClose(ws: ServerWebSocket) { + this.clients.delete(ws); + logger.debug('WebSocket client disconnected'); + } + + handleMessage(ws: ServerWebSocket, message: string | Buffer) { + // Handle any client messages if needed + } + + broadcastSubmissions(submissions: TwitterSubmission[]) { + const message = JSON.stringify({ + type: 'update', + data: submissions, + }); + + for (const client of this.clients) { + client.send(message); + } + } +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 69a6b35..c4a69a2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,16 @@ import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import SubmissionList from './components/SubmissionList'; +import { LiveUpdateProvider } from './contexts/LiveUpdateContext'; function App() { return ( - - - } /> - - + + + + } /> + + + ); } diff --git a/frontend/src/components/SubmissionList.tsx b/frontend/src/components/SubmissionList.tsx index a62c3f0..10d019a 100644 --- a/frontend/src/components/SubmissionList.tsx +++ b/frontend/src/components/SubmissionList.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import axios from 'axios'; import { TwitterSubmission } from '../types/twitter'; +import { useLiveUpdates } from '../contexts/LiveUpdateContext'; const StatusBadge = ({ status }: { status: TwitterSubmission['status'] }) => { const className = `status-badge status-${status}`; @@ -12,6 +13,7 @@ const SubmissionList = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [filter, setFilter] = useState('all'); + const { connected, lastUpdate } = useLiveUpdates(); const fetchSubmissions = async () => { try { @@ -30,13 +32,21 @@ const SubmissionList = () => { } }; + // Initial fetch useEffect(() => { fetchSubmissions(); - // Set up auto-refresh every 30 seconds - const interval = setInterval(fetchSubmissions, 30000); - return () => clearInterval(interval); }, [filter]); + // Handle live updates + useEffect(() => { + if (lastUpdate?.type === 'update') { + const updatedSubmissions = filter === 'all' + ? lastUpdate.data + : lastUpdate.data.filter(s => s.status === filter); + setSubmissions(updatedSubmissions); + } + }, [lastUpdate, filter]); + const getTweetUrl = (tweetId: string, username: string) => { return `https://x.com/${username}/status/${tweetId}`; }; @@ -71,20 +81,28 @@ const SubmissionList = () => {

Public Goods News Submissions

-
- {(['all', 'pending', 'approved', 'rejected'] as const).map((status) => ( - - ))} +
+
+
+ + {connected ? 'Live Updates' : 'Connecting...'} + +
+
+ {(['all', 'pending', 'approved', 'rejected'] as const).map((status) => ( + + ))} +
diff --git a/frontend/src/contexts/LiveUpdateContext.tsx b/frontend/src/contexts/LiveUpdateContext.tsx new file mode 100644 index 0000000..0676d0c --- /dev/null +++ b/frontend/src/contexts/LiveUpdateContext.tsx @@ -0,0 +1,75 @@ +import { createContext, useContext, useEffect, useState, ReactNode } from 'react'; +import { TwitterSubmission } from '../types/twitter'; + +type LiveUpdateContextType = { + connected: boolean; + lastUpdate: { type: string; data: TwitterSubmission[] } | null; +}; + +const LiveUpdateContext = createContext(undefined); + +export function LiveUpdateProvider({ children }: { children: ReactNode }) { + const [connected, setConnected] = useState(false); + const [lastUpdate, setLastUpdate] = useState<{ type: string; data: TwitterSubmission[] } | null>(null); + + useEffect(() => { + let ws: WebSocket; + let reconnectTimer: number; + + const connect = () => { + // Use the proxied WebSocket path + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const wsUrl = `${protocol}//${window.location.host}/ws`; + ws = new WebSocket(wsUrl); + + ws.onopen = () => { + console.log('Live updates connected'); + setConnected(true); + }; + + ws.onclose = () => { + console.log('Live updates disconnected'); + setConnected(false); + // Try to reconnect after 3 seconds + reconnectTimer = window.setTimeout(connect, 3000); + }; + + ws.onerror = (error) => { + console.error('Live updates error:', error); + }; + + ws.onmessage = (event) => { + try { + const data = JSON.parse(event.data); + if (data.type === 'update') { + setLastUpdate(data); + } + } catch (error) { + console.error('Error processing update:', error); + } + }; + }; + + connect(); + + // Cleanup function + return () => { + window.clearTimeout(reconnectTimer); + ws?.close(); + }; + }, []); // Empty dependency array - only run once on mount + + return ( + + {children} + + ); +} + +export function useLiveUpdates() { + const context = useContext(LiveUpdateContext); + if (context === undefined) { + throw new Error('useLiveUpdates must be used within a LiveUpdateProvider'); + } + return context; +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 4172b9c..0ca537a 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,7 +6,11 @@ export default defineConfig({ plugins: [react()], server: { proxy: { - '/api': 'http://localhost:3000' + '/api': 'http://localhost:3000', + '/ws': { + target: 'ws://localhost:3000', + ws: true + } } }, build: { From f7350604ff88e489ae00e3d618b1191be8e8ada4 Mon Sep 17 00:00:00 2001 From: Elliot Braem Date: Thu, 19 Dec 2024 14:06:40 -0600 Subject: [PATCH 13/13] prettier --- backend/.prettierrc => .prettierrc | 0 backend/package.json | 5 +- backend/src/config/admins.ts | 2 +- backend/src/config/config.ts | 12 +- backend/src/index.ts | 148 +++++++++++------ backend/src/services/db/index.ts | 170 +++++++++++++------- backend/src/services/twitter/client.ts | 144 +++++++++++------ backend/src/services/websocket/index.ts | 42 ----- backend/src/utils/cache.ts | 20 +-- backend/src/utils/logger.ts | 20 +-- frontend/eslint.config.js | 24 +-- frontend/postcss.config.js | 2 +- frontend/src/App.tsx | 6 +- frontend/src/components/SubmissionList.tsx | 91 ++++++----- frontend/src/contexts/LiveUpdateContext.tsx | 40 +++-- frontend/src/main.tsx | 12 +- frontend/src/types/twitter.ts | 2 +- frontend/tailwind.config.js | 11 +- frontend/vite.config.ts | 24 +-- package.json | 7 +- 20 files changed, 460 insertions(+), 322 deletions(-) rename backend/.prettierrc => .prettierrc (100%) delete mode 100644 backend/src/services/websocket/index.ts diff --git a/backend/.prettierrc b/.prettierrc similarity index 100% rename from backend/.prettierrc rename to .prettierrc diff --git a/backend/package.json b/backend/package.json index 95070db..007e25a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,9 +6,7 @@ "scripts": { "build": "bun build ./src/index.ts --target=bun --outdir=dist --format=esm", "start": "bun run dist/index.js", - "dev": "bun run --watch src/index.ts", - "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", - "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" + "dev": "bun run --watch src/index.ts" }, "browserslist": { "production": [ @@ -29,7 +27,6 @@ "@types/ora": "^3.2.0", "bun-types": "^1.1.40", "jest": "^29.7.0", - "prettier": "^3.3.3", "ts-node": "^10.9.1", "typescript": "^5.3.3" }, diff --git a/backend/src/config/admins.ts b/backend/src/config/admins.ts index d1003ba..8c3deef 100644 --- a/backend/src/config/admins.ts +++ b/backend/src/config/admins.ts @@ -1,5 +1,5 @@ export const ADMIN_ACCOUNTS: string[] = [ - "elliot_braem" + "elliot_braem", // Add admin Twitter handles here (without @) // Example: "TwitterDev" ]; diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts index 4cd0dae..8125d81 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config/config.ts @@ -4,7 +4,7 @@ const config: AppConfig = { twitter: { username: process.env.TWITTER_USERNAME!, password: process.env.TWITTER_PASSWORD!, - email: process.env.TWITTER_EMAIL! + email: process.env.TWITTER_EMAIL!, }, environment: (process.env.NODE_ENV as "development" | "production" | "test") || @@ -13,8 +13,14 @@ const config: AppConfig = { export function validateEnv() { // Validate required Twitter credentials - if (!process.env.TWITTER_USERNAME || !process.env.TWITTER_PASSWORD || !process.env.TWITTER_EMAIL) { - throw new Error('Missing required Twitter credentials. Please ensure TWITTER_USERNAME, TWITTER_PASSWORD, and TWITTER_EMAIL are set in your environment variables.'); + if ( + !process.env.TWITTER_USERNAME || + !process.env.TWITTER_PASSWORD || + !process.env.TWITTER_EMAIL + ) { + throw new Error( + "Missing required Twitter credentials. Please ensure TWITTER_USERNAME, TWITTER_PASSWORD, and TWITTER_EMAIL are set in your environment variables.", + ); } } diff --git a/backend/src/index.ts b/backend/src/index.ts index c501946..ede15df 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,37 +1,45 @@ +import { ServerWebSocket } from "bun"; import dotenv from "dotenv"; import path from "path"; -import { TwitterService } from "./services/twitter/client"; -import { db } from "./services/db"; -import { WebSocketService } from "./services/websocket"; import config, { validateEnv } from "./config/config"; -import { - logger, - startSpinner, - succeedSpinner, - failSpinner, - cleanup +import { db } from "./services/db"; +import { TwitterService } from "./services/twitter/client"; +import { + cleanup, + failSpinner, + logger, + startSpinner, + succeedSpinner, } from "./utils/logger"; -const PORT = Number(process.env.PORT) || 3000; +const PORT = Number(process.env.PORT) || 3001; // Use different port for testing + +// Store active WebSocket connections +const activeConnections = new Set(); + +// Broadcast to all connected clients +export function broadcastUpdate(data: unknown) { + const message = JSON.stringify(data); + activeConnections.forEach((ws) => { + try { + ws.send(message); + } catch (error) { + logger.error("Error broadcasting to WebSocket client:", error); + activeConnections.delete(ws); + } + }); +} async function main() { try { // Load environment variables - startSpinner('env', 'Loading environment variables...'); + startSpinner("env", "Loading environment variables..."); dotenv.config(); validateEnv(); - succeedSpinner('env', 'Environment variables loaded'); - - // Initialize Twitter service - startSpinner('twitter-init', 'Initializing Twitter service...'); - const twitterService = new TwitterService(config.twitter); - await twitterService.initialize(); - succeedSpinner('twitter-init', 'Twitter service initialized'); + succeedSpinner("env", "Environment variables loaded"); // Initialize services - startSpinner('server', 'Starting server...'); - const wsService = new WebSocketService(); - db.setWebSocketService(wsService); + startSpinner("server", "Starting server..."); const server = Bun.serve({ port: PORT, @@ -39,21 +47,25 @@ async function main() { const url = new URL(req.url); // WebSocket upgrade - if (url.pathname === '/ws') { + if (url.pathname === "/ws") { if (server.upgrade(req)) { return; } - return new Response('WebSocket upgrade failed', { status: 500 }); + return new Response("WebSocket upgrade failed", { status: 500 }); } // API Routes - if (url.pathname.startsWith('/api')) { + if (url.pathname.startsWith("/api")) { try { - if (url.pathname === '/api/submissions') { - const status = url.searchParams.get('status') as "pending" | "approved" | "rejected" | null; - const submissions = status ? - db.getSubmissionsByStatus(status) : - db.getAllSubmissions(); + if (url.pathname === "/api/submissions") { + const status = url.searchParams.get("status") as + | "pending" + | "approved" + | "rejected" + | null; + const submissions = status + ? db.getSubmissionsByStatus(status) + : db.getAllSubmissions(); return Response.json(submissions); } @@ -62,70 +74,102 @@ async function main() { const tweetId = match[1]; const submission = db.getSubmission(tweetId); if (!submission) { - return Response.json({ error: 'Submission not found' }, { status: 404 }); + return Response.json( + { error: "Submission not found" }, + { status: 404 }, + ); } return Response.json(submission); } } catch (error) { - return Response.json({ error: 'Internal server error' }, { status: 500 }); + return Response.json( + { error: "Internal server error" }, + { status: 500 }, + ); } } - // Serve static frontend files in production - if (process.env.NODE_ENV === 'production') { - const filePath = url.pathname === '/' ? '/index.html' : url.pathname; - const file = Bun.file(path.join(__dirname, '../../frontend/dist', filePath)); + // Serve static frontend files in production only + if (process.env.NODE_ENV === "production") { + const filePath = url.pathname === "/" ? "/index.html" : url.pathname; + const file = Bun.file( + path.join(__dirname, "../../frontend/dist", filePath), + ); if (await file.exists()) { return new Response(file); } // Fallback to index.html for client-side routing - return new Response(Bun.file(path.join(__dirname, '../../frontend/dist/index.html'))); + return new Response( + Bun.file(path.join(__dirname, "../../frontend/dist/index.html")), + ); } - return new Response('Not found', { status: 404 }); + return new Response("Not found", { status: 404 }); + }, + websocket: { + open: (ws: ServerWebSocket) => { + activeConnections.add(ws); + logger.debug( + `WebSocket client connected. Total connections: ${activeConnections.size}`, + ); + }, + close: (ws: ServerWebSocket) => { + activeConnections.delete(ws); + logger.debug( + `WebSocket client disconnected. Total connections: ${activeConnections.size}`, + ); + }, + message: (ws: ServerWebSocket, message: string | Buffer) => { + // we don't care about two-way connection yet + }, }, - websocket: wsService.getWebSocketConfig(), }); - succeedSpinner('server', `Server running on port ${PORT}`); + + succeedSpinner("server", `Server running on port ${PORT}`); + + // Initialize Twitter service after server is running + startSpinner("twitter-init", "Initializing Twitter service..."); + const twitterService = new TwitterService(config.twitter); + await twitterService.initialize(); + succeedSpinner("twitter-init", "Twitter service initialized"); // Handle graceful shutdown process.on("SIGINT", async () => { - startSpinner('shutdown', 'Shutting down gracefully...'); + startSpinner("shutdown", "Shutting down gracefully..."); try { await twitterService.stop(); - succeedSpinner('shutdown', 'Shutdown complete'); + succeedSpinner("shutdown", "Shutdown complete"); process.exit(0); } catch (error) { - failSpinner('shutdown', 'Error during shutdown'); - logger.error('Shutdown', error); + failSpinner("shutdown", "Error during shutdown"); + logger.error("Shutdown", error); process.exit(1); } }); - logger.info('๐Ÿš€ Bot is running and ready for events', { + logger.info("๐Ÿš€ Bot is running and ready for events", { twitterEnabled: true, - websocketEnabled: true + websocketEnabled: true, }); // Start checking for mentions - startSpinner('twitter-mentions', 'Starting mentions check...'); + startSpinner("twitter-mentions", "Starting mentions check..."); await twitterService.startMentionsCheck(); - succeedSpinner('twitter-mentions', 'Mentions check started'); - + succeedSpinner("twitter-mentions", "Mentions check started"); } catch (error) { // Handle any initialization errors - ['env', 'websocket', 'twitter-init', 'twitter-mentions', 'express'].forEach(key => { + ["env", "twitter-init", "twitter-mentions", "server"].forEach((key) => { failSpinner(key, `Failed during ${key}`); }); - logger.error('Startup', error); + logger.error("Startup", error); cleanup(); process.exit(1); } } // Start the application -logger.info('Starting Public Goods News Bot...'); +logger.info("Starting Public Goods News Bot..."); main().catch((error) => { - logger.error('Unhandled Exception', error); + logger.error("Unhandled Exception", error); process.exit(1); }); diff --git a/backend/src/services/db/index.ts b/backend/src/services/db/index.ts index 62db2ae..3f8dce0 100644 --- a/backend/src/services/db/index.ts +++ b/backend/src/services/db/index.ts @@ -3,12 +3,13 @@ import { TwitterSubmission, Moderation } from "../../types"; import { mkdir } from "node:fs/promises"; import { join, dirname } from "node:path"; import { existsSync } from "node:fs"; -import { WebSocketService } from "../websocket"; +import { broadcastUpdate } from "../../index"; export class DatabaseService { private db: Database; - private wsService?: WebSocketService; - private static readonly DB_PATH = process.env.DATABASE_URL?.replace('file:', '') || join('.db', 'submissions.sqlite'); + private static readonly DB_PATH = + process.env.DATABASE_URL?.replace("file:", "") || + join(".db", "submissions.sqlite"); constructor() { this.ensureDbDirectory(); @@ -16,14 +17,12 @@ export class DatabaseService { this.initialize(); } - setWebSocketService(wsService: WebSocketService) { - this.wsService = wsService; - } - private notifyUpdate() { - if (this.wsService) { - this.wsService.broadcastSubmissions(this.getAllSubmissions()); - } + const submissions = this.getAllSubmissions(); + broadcastUpdate({ + type: "update", + data: submissions, + }); } private ensureDbDirectory() { @@ -95,7 +94,9 @@ export class DatabaseService { // Add new columns if they don't exist try { - this.db.run(`ALTER TABLE submissions ADD COLUMN username TEXT NOT NULL DEFAULT ''`); + this.db.run( + `ALTER TABLE submissions ADD COLUMN username TEXT NOT NULL DEFAULT ''`, + ); } catch (e) { // Column might already exist } @@ -119,7 +120,7 @@ export class DatabaseService { submission.description || null, submission.status, submission.acknowledgmentTweetId || null, - submission.createdAt + submission.createdAt, ); this.notifyUpdate(); @@ -136,25 +137,35 @@ export class DatabaseService { moderation.tweetId, moderation.adminId, moderation.action, - moderation.timestamp.toISOString() + moderation.timestamp.toISOString(), ); this.notifyUpdate(); } - updateSubmissionStatus(tweetId: string, status: TwitterSubmission['status'], moderationResponseTweetId: string): void { - this.db.prepare(` + updateSubmissionStatus( + tweetId: string, + status: TwitterSubmission["status"], + moderationResponseTweetId: string, + ): void { + this.db + .prepare( + ` UPDATE submissions SET status = ?, moderation_response_tweet_id = ? WHERE tweet_id = ? - `).run(status, moderationResponseTweetId, tweetId); + `, + ) + .run(status, moderationResponseTweetId, tweetId); this.notifyUpdate(); } getSubmission(tweetId: string): TwitterSubmission | null { - const submission = this.db.prepare(` + const submission = this.db + .prepare( + ` SELECT s.*, GROUP_CONCAT( json_object( 'adminId', m.admin_id, @@ -167,7 +178,9 @@ export class DatabaseService { LEFT JOIN moderation_history m ON s.tweet_id = m.tweet_id WHERE s.tweet_id = ? GROUP BY s.tweet_id - `).get(tweetId) as any; + `, + ) + .get(tweetId) as any; if (!submission) return null; @@ -183,17 +196,21 @@ export class DatabaseService { acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, createdAt: submission.created_at, - moderationHistory: submission.moderation_history + moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, - timestamp: new Date(m.timestamp) + timestamp: new Date(m.timestamp), })) - : [] + : [], }; } - getSubmissionByAcknowledgmentTweetId(acknowledgmentTweetId: string): TwitterSubmission | null { - const submission = this.db.prepare(` + getSubmissionByAcknowledgmentTweetId( + acknowledgmentTweetId: string, + ): TwitterSubmission | null { + const submission = this.db + .prepare( + ` SELECT s.*, GROUP_CONCAT( json_object( 'adminId', m.admin_id, @@ -206,7 +223,9 @@ export class DatabaseService { LEFT JOIN moderation_history m ON s.tweet_id = m.tweet_id WHERE s.acknowledgment_tweet_id = ? GROUP BY s.tweet_id - `).get(acknowledgmentTweetId) as any; + `, + ) + .get(acknowledgmentTweetId) as any; if (!submission) return null; @@ -222,17 +241,19 @@ export class DatabaseService { acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, createdAt: submission.created_at, - moderationHistory: submission.moderation_history + moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, - timestamp: new Date(m.timestamp) + timestamp: new Date(m.timestamp), })) - : [] + : [], }; } getAllSubmissions(): TwitterSubmission[] { - const submissions = this.db.prepare(` + const submissions = this.db + .prepare( + ` SELECT s.*, GROUP_CONCAT( json_object( 'adminId', m.admin_id, @@ -244,9 +265,11 @@ export class DatabaseService { FROM submissions s LEFT JOIN moderation_history m ON s.tweet_id = m.tweet_id GROUP BY s.tweet_id - `).all() as any[]; + `, + ) + .all() as any[]; - return submissions.map(submission => ({ + return submissions.map((submission) => ({ tweetId: submission.tweet_id, userId: submission.user_id, username: submission.username, @@ -258,17 +281,21 @@ export class DatabaseService { acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, createdAt: submission.created_at, - moderationHistory: submission.moderation_history + moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, - timestamp: new Date(m.timestamp) + timestamp: new Date(m.timestamp), })) - : [] + : [], })); } - getSubmissionsByStatus(status: TwitterSubmission['status']): TwitterSubmission[] { - const submissions = this.db.prepare(` + getSubmissionsByStatus( + status: TwitterSubmission["status"], + ): TwitterSubmission[] { + const submissions = this.db + .prepare( + ` SELECT s.*, GROUP_CONCAT( json_object( 'adminId', m.admin_id, @@ -281,9 +308,11 @@ export class DatabaseService { LEFT JOIN moderation_history m ON s.tweet_id = m.tweet_id WHERE s.status = ? GROUP BY s.tweet_id - `).all(status) as any[]; + `, + ) + .all(status) as any[]; - return submissions.map(submission => ({ + return submissions.map((submission) => ({ tweetId: submission.tweet_id, userId: submission.user_id, username: submission.username, @@ -295,38 +324,48 @@ export class DatabaseService { acknowledgmentTweetId: submission.acknowledgment_tweet_id, moderationResponseTweetId: submission.moderation_response_tweet_id, createdAt: submission.created_at, - moderationHistory: submission.moderation_history + moderationHistory: submission.moderation_history ? JSON.parse(`[${submission.moderation_history}]`).map((m: any) => ({ ...m, - timestamp: new Date(m.timestamp) + timestamp: new Date(m.timestamp), })) - : [] + : [], })); } // Rate limiting methods getDailySubmissionCount(userId: string): number { - const today = new Date().toISOString().split('T')[0]; - + const today = new Date().toISOString().split("T")[0]; + // Clean up old entries first - this.db.prepare(` + this.db + .prepare( + ` DELETE FROM submission_counts WHERE last_reset_date < ? - `).run(today); + `, + ) + .run(today); - const result = this.db.prepare(` + const result = this.db + .prepare( + ` SELECT count FROM submission_counts WHERE user_id = ? AND last_reset_date = ? - `).get(userId, today) as { count: number } | undefined; + `, + ) + .get(userId, today) as { count: number } | undefined; return result?.count || 0; } incrementDailySubmissionCount(userId: string): void { - const today = new Date().toISOString().split('T')[0]; + const today = new Date().toISOString().split("T")[0]; - this.db.prepare(` + this.db + .prepare( + ` INSERT INTO submission_counts (user_id, count, last_reset_date) VALUES (?, 1, ?) ON CONFLICT(user_id) DO UPDATE SET @@ -335,37 +374,54 @@ export class DatabaseService { ELSE count + 1 END, last_reset_date = ? - `).run(userId, today, today, today); + `, + ) + .run(userId, today, today, today); } // Last checked tweet ID methods getLastCheckedTweetId(): string | null { - const result = this.db.prepare(` + const result = this.db + .prepare( + ` SELECT value FROM twitter_state WHERE key = 'last_checked_tweet_id' - `).get() as { value: string } | undefined; + `, + ) + .get() as { value: string } | undefined; return result?.value || null; } saveLastCheckedTweetId(tweetId: string): void { - this.db.prepare(` + this.db + .prepare( + ` INSERT INTO twitter_state (key, value, updated_at) VALUES ('last_checked_tweet_id', ?, CURRENT_TIMESTAMP) ON CONFLICT(key) DO UPDATE SET value = ?, updated_at = CURRENT_TIMESTAMP - `).run(tweetId, tweetId); + `, + ) + .run(tweetId, tweetId); } - updateSubmissionAcknowledgment(tweetId: string, acknowledgmentTweetId: string): void { - this.db.prepare(` + updateSubmissionAcknowledgment( + tweetId: string, + acknowledgmentTweetId: string, + ): void { + this.db + .prepare( + ` UPDATE submissions SET acknowledgment_tweet_id = ? WHERE tweet_id = ? - `).run(acknowledgmentTweetId, tweetId); - + `, + ) + .run(acknowledgmentTweetId, tweetId); + this.notifyUpdate(); } } diff --git a/backend/src/services/twitter/client.ts b/backend/src/services/twitter/client.ts index c402c9f..a7f1313 100644 --- a/backend/src/services/twitter/client.ts +++ b/backend/src/services/twitter/client.ts @@ -1,8 +1,12 @@ import { Scraper, SearchMode, Tweet } from "agent-twitter-client"; -import { TwitterSubmission, Moderation, TwitterConfig } from "../../types/twitter"; +import { + TwitterSubmission, + Moderation, + TwitterConfig, +} from "../../types/twitter"; import { logger } from "../../utils/logger"; import { db } from "../db"; -import { +import { TwitterCookie, ensureCacheDirectory, getCachedCookies, @@ -29,9 +33,11 @@ export class TwitterService { private async setCookiesFromArray(cookiesArray: TwitterCookie[]) { const cookieStrings = cookiesArray.map( (cookie) => - `${cookie.key}=${cookie.value}; Domain=${cookie.domain}; Path=${cookie.path}; ${cookie.secure ? "Secure" : "" - }; ${cookie.httpOnly ? "HttpOnly" : ""}; SameSite=${cookie.sameSite || "Lax" - }` + `${cookie.key}=${cookie.value}; Domain=${cookie.domain}; Path=${cookie.path}; ${ + cookie.secure ? "Secure" : "" + }; ${cookie.httpOnly ? "HttpOnly" : ""}; SameSite=${ + cookie.sameSite || "Lax" + }`, ); await this.client.setCookies(cookieStrings); } @@ -67,7 +73,7 @@ export class TwitterService { this.lastCheckedTweetId = db.getLastCheckedTweetId(); // Try to login with retries - logger.info('Attempting Twitter login...'); + logger.info("Attempting Twitter login..."); while (true) { try { await this.client.login( @@ -83,7 +89,7 @@ export class TwitterService { break; } } catch (error) { - logger.error('Failed to login to Twitter, retrying...', error); + logger.error("Failed to login to Twitter, retrying...", error); } // Wait before retrying @@ -94,9 +100,9 @@ export class TwitterService { await this.initializeAdminIds(); this.isInitialized = true; - logger.info('Successfully logged in to Twitter'); + logger.info("Successfully logged in to Twitter"); } catch (error) { - logger.error('Failed to initialize Twitter client:', error); + logger.error("Failed to initialize Twitter client:", error); throw error; } } @@ -115,7 +121,9 @@ export class TwitterService { `@${this.twitterUsername}`, BATCH_SIZE, SearchMode.Latest, - allNewTweets.length > 0 ? allNewTweets[allNewTweets.length - 1].id : undefined + allNewTweets.length > 0 + ? allNewTweets[allNewTweets.length - 1].id + : undefined, ) ).tweets; @@ -124,8 +132,11 @@ export class TwitterService { // Check if any tweet in this batch is older than or equal to our last checked ID for (const tweet of batch) { if (!tweet.id) continue; - - if (!this.lastCheckedTweetId || BigInt(tweet.id) > BigInt(this.lastCheckedTweetId)) { + + if ( + !this.lastCheckedTweetId || + BigInt(tweet.id) > BigInt(this.lastCheckedTweetId) + ) { allNewTweets.push(tweet); } else { foundOldTweet = true; @@ -136,50 +147,54 @@ export class TwitterService { if (batch.length < BATCH_SIZE) break; // Last batch was partial, no more to fetch attempts++; } catch (error) { - logger.error('Error fetching mentions batch:', error); + logger.error("Error fetching mentions batch:", error); break; } } // Sort all fetched tweets by ID (chronologically) return allNewTweets.sort((a, b) => { - const aId = BigInt(a.id || '0'); - const bId = BigInt(b.id || '0'); + const aId = BigInt(a.id || "0"); + const bId = BigInt(b.id || "0"); return aId > bId ? 1 : aId < bId ? -1 : 0; }); } async startMentionsCheck() { - logger.info('Listening for mentions...'); - + logger.info("Listening for mentions..."); + // Check mentions every minute this.checkInterval = setInterval(async () => { if (!this.isInitialized) return; try { - logger.info('Checking mentions...'); - + logger.info("Checking mentions..."); + const newTweets = await this.fetchAllNewMentions(); if (newTweets.length === 0) { - logger.info('No new mentions'); + logger.info("No new mentions"); } else { logger.info(`Found ${newTweets.length} new mentions`); // Process new tweets for (const tweet of newTweets) { if (!tweet.id) continue; - + try { if (this.isSubmission(tweet)) { - logger.info(`Received new submission: ${this.getTweetLink(tweet.id, tweet.username)}`); + logger.info( + `Received new submission: ${this.getTweetLink(tweet.id, tweet.username)}`, + ); await this.handleSubmission(tweet); } else if (this.isModeration(tweet)) { - logger.info(`Received new moderation: ${this.getTweetLink(tweet.id, tweet.username)}`); + logger.info( + `Received new moderation: ${this.getTweetLink(tweet.id, tweet.username)}`, + ); await this.handleModeration(tweet); } } catch (error) { - logger.error('Error processing tweet:', error); + logger.error("Error processing tweet:", error); } } @@ -191,7 +206,7 @@ export class TwitterService { } } } catch (error) { - logger.error('Error checking mentions:', error); + logger.error("Error checking mentions:", error); } }, 60000); // Check every minute } @@ -212,7 +227,9 @@ export class TwitterService { // Get the tweet being replied to const inReplyToId = tweet.inReplyToStatusId; if (!inReplyToId) { - logger.error(`Submission tweet ${tweet.id} is not a reply to another tweet`); + logger.error( + `Submission tweet ${tweet.id} is not a reply to another tweet`, + ); return; } @@ -230,7 +247,7 @@ export class TwitterService { if (dailyCount >= this.DAILY_SUBMISSION_LIMIT) { await this.replyToTweet( tweet.id, - "You've reached your daily submission limit. Please try again tomorrow." + "You've reached your daily submission limit. Please try again tomorrow.", ); logger.info(`User ${userId} has reached limit, replied to submission.`); return; @@ -239,13 +256,14 @@ export class TwitterService { // Create submission using the original tweet's content const submission: TwitterSubmission = { tweetId: originalTweet.id!, // The tweet being submitted - userId: originalTweet.userId!, - username: originalTweet.username!, + userId: originalTweet.userId!, + username: originalTweet.username!, content: originalTweet.text || "", hashtags: originalTweet.hashtags || [], status: "pending", moderationHistory: [], - createdAt: originalTweet.timeParsed?.toISOString() || new Date().toISOString(), + createdAt: + originalTweet.timeParsed?.toISOString() || new Date().toISOString(), }; // Save submission to database @@ -256,14 +274,21 @@ export class TwitterService { // Send acknowledgment and save its ID const acknowledgmentTweetId = await this.replyToTweet( tweet.id, // Reply to the submission tweet - "Successfully submitted to publicgoods.news!" + "Successfully submitted to publicgoods.news!", ); - + if (acknowledgmentTweetId) { - db.updateSubmissionAcknowledgment(originalTweet.id!, acknowledgmentTweetId); - logger.info(`Successfully submitted. Sent reply: ${this.getTweetLink(acknowledgmentTweetId)}`) + db.updateSubmissionAcknowledgment( + originalTweet.id!, + acknowledgmentTweetId, + ); + logger.info( + `Successfully submitted. Sent reply: ${this.getTweetLink(acknowledgmentTweetId)}`, + ); } else { - logger.error(`Failed to acknowledge submission: ${this.getTweetLink(tweet.id, tweet.username)}`) + logger.error( + `Failed to acknowledge submission: ${this.getTweetLink(tweet.id, tweet.username)}`, + ); } } catch (error) { logger.error(`Error handling submission for tweet ${tweet.id}:`, error); @@ -276,7 +301,7 @@ export class TwitterService { // Verify admin status using cached ID if (!this.isAdmin(userId)) { - logger.info(`User ${userId} is not admin.`) + logger.info(`User ${userId} is not admin.`); return; // Silently ignore non-admin moderation attempts } @@ -290,7 +315,9 @@ export class TwitterService { // Check if submission has already been moderated by any admin if (submission.moderationHistory.length > 0) { - logger.info(`Submission ${submission.tweetId} has already been moderated, ignoring new moderation attempt.`); + logger.info( + `Submission ${submission.tweetId} has already been moderated, ignoring new moderation attempt.`, + ); return; } @@ -308,10 +335,14 @@ export class TwitterService { // Process the moderation action if (action === "approve") { - logger.info(`Received review from Admin ${this.adminIdCache.get(userId)}, processing approval.`) + logger.info( + `Received review from Admin ${this.adminIdCache.get(userId)}, processing approval.`, + ); await this.processApproval(submission); } else { - logger.info(`Received review from Admin ${this.adminIdCache.get(userId)}, processing rejection.`) + logger.info( + `Received review from Admin ${this.adminIdCache.get(userId)}, processing rejection.`, + ); await this.processRejection(submission); } } @@ -320,25 +351,33 @@ export class TwitterService { // TODO: Add NEAR integration here for approved submissions const responseTweetId = await this.replyToTweet( submission.tweetId, - "Your submission has been approved and will be added to the public goods news feed!" + "Your submission has been approved and will be added to the public goods news feed!", ); if (responseTweetId) { - db.updateSubmissionStatus(submission.tweetId, "approved", responseTweetId); + db.updateSubmissionStatus( + submission.tweetId, + "approved", + responseTweetId, + ); } } private async processRejection(submission: TwitterSubmission): Promise { const responseTweetId = await this.replyToTweet( submission.tweetId, - "Your submission has been reviewed and was not accepted for the public goods news feed." + "Your submission has been reviewed and was not accepted for the public goods news feed.", ); if (responseTweetId) { - db.updateSubmissionStatus(submission.tweetId, "rejected", responseTweetId); + db.updateSubmissionStatus( + submission.tweetId, + "rejected", + responseTweetId, + ); } } private getModerationAction(tweet: Tweet): "approve" | "reject" | null { - const hashtags = tweet.hashtags?.map(tag => tag.toLowerCase()) || []; + const hashtags = tweet.hashtags?.map((tag) => tag.toLowerCase()) || []; if (hashtags.includes("approve")) return "approve"; if (hashtags.includes("reject")) return "reject"; return null; @@ -352,20 +391,27 @@ export class TwitterService { return tweet.text?.toLowerCase().includes("!submit") || false; } - private async replyToTweet(tweetId: string, message: string): Promise { + private async replyToTweet( + tweetId: string, + message: string, + ): Promise { try { const response = await this.client.sendTweet(message, tweetId); - const responseData = await response.json() as any; + const responseData = (await response.json()) as any; // Extract tweet ID from response - const replyTweetId = responseData?.data?.create_tweet?.tweet_results?.result?.rest_id; + const replyTweetId = + responseData?.data?.create_tweet?.tweet_results?.result?.rest_id; return replyTweetId || null; } catch (error) { - logger.error('Error replying to tweet:', error); + logger.error("Error replying to tweet:", error); return null; } } - private getTweetLink(tweetId: string, username: string = this.twitterUsername): string { + private getTweetLink( + tweetId: string, + username: string = this.twitterUsername, + ): string { return `https://x.com/${username}/status/${tweetId}`; } } diff --git a/backend/src/services/websocket/index.ts b/backend/src/services/websocket/index.ts deleted file mode 100644 index c46e8d1..0000000 --- a/backend/src/services/websocket/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ServerWebSocket, Server } from "bun"; -import { TwitterSubmission } from "../../types"; -import { logger } from "../../utils/logger"; - -export class WebSocketService { - private clients = new Set>(); - - constructor() {} - - getWebSocketConfig() { - return { - open: (ws: ServerWebSocket) => this.handleOpen(ws), - close: (ws: ServerWebSocket) => this.handleClose(ws), - message: (ws: ServerWebSocket, message: string | Buffer) => this.handleMessage(ws, message), - }; - } - - handleOpen(ws: ServerWebSocket) { - this.clients.add(ws); - logger.debug('WebSocket client connected'); - } - - handleClose(ws: ServerWebSocket) { - this.clients.delete(ws); - logger.debug('WebSocket client disconnected'); - } - - handleMessage(ws: ServerWebSocket, message: string | Buffer) { - // Handle any client messages if needed - } - - broadcastSubmissions(submissions: TwitterSubmission[]) { - const message = JSON.stringify({ - type: 'update', - data: submissions, - }); - - for (const client of this.clients) { - client.send(message); - } - } -} diff --git a/backend/src/utils/cache.ts b/backend/src/utils/cache.ts index 7956aa0..380ba28 100644 --- a/backend/src/utils/cache.ts +++ b/backend/src/utils/cache.ts @@ -16,7 +16,7 @@ interface CookieCache { [username: string]: TwitterCookie[]; } -const CACHE_DIR = process.env.CACHE_DIR || '.cache'; +const CACHE_DIR = process.env.CACHE_DIR || ".cache"; export async function ensureCacheDirectory() { try { @@ -25,20 +25,22 @@ export async function ensureCacheDirectory() { } catch { // Directory doesn't exist, create it await fs.mkdir(CACHE_DIR, { recursive: true }); - logger.info('Created cache directory'); + logger.info("Created cache directory"); } } catch (error) { - logger.error('Failed to create cache directory:', error); + logger.error("Failed to create cache directory:", error); throw error; } } -export async function getCachedCookies(username: string): Promise { +export async function getCachedCookies( + username: string, +): Promise { try { // Try to read cookies from a local cache file - const cookiePath = path.join(CACHE_DIR, '.twitter-cookies.json'); + const cookiePath = path.join(CACHE_DIR, ".twitter-cookies.json"); - const data = await fs.readFile(cookiePath, 'utf-8'); + const data = await fs.readFile(cookiePath, "utf-8"); const cache: CookieCache = JSON.parse(data); if (cache[username]) { @@ -53,11 +55,11 @@ export async function getCachedCookies(username: string): Promise { } spinners[key] = ora({ text: `${text}\n`, - color: 'cyan', - spinner: 'dots', + color: "cyan", + spinner: "dots", }).start(); }; @@ -95,8 +95,8 @@ export const cleanup = (): void => { }; // Register cleanup on process exit -process.on('exit', cleanup); -process.on('SIGINT', () => { +process.on("exit", cleanup); +process.on("SIGINT", () => { cleanup(); process.exit(0); }); diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 092408a..79a552e 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,28 +1,28 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; export default tseslint.config( - { ignores: ['dist'] }, + { ignores: ["dist"] }, { extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], + files: ["**/*.{ts,tsx}"], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, + "react-hooks": reactHooks, + "react-refresh": reactRefresh, }, rules: { ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', + "react-refresh/only-export-components": [ + "warn", { allowConstantExport: true }, ], }, }, -) +); diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 2e7af2b..2aa7205 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -3,4 +3,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, -} +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c4a69a2..551c138 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,6 +1,6 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import SubmissionList from './components/SubmissionList'; -import { LiveUpdateProvider } from './contexts/LiveUpdateContext'; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import SubmissionList from "./components/SubmissionList"; +import { LiveUpdateProvider } from "./contexts/LiveUpdateContext"; function App() { return ( diff --git a/frontend/src/components/SubmissionList.tsx b/frontend/src/components/SubmissionList.tsx index 10d019a..25b936c 100644 --- a/frontend/src/components/SubmissionList.tsx +++ b/frontend/src/components/SubmissionList.tsx @@ -1,9 +1,9 @@ -import { useEffect, useState } from 'react'; -import axios from 'axios'; -import { TwitterSubmission } from '../types/twitter'; -import { useLiveUpdates } from '../contexts/LiveUpdateContext'; +import { useEffect, useState } from "react"; +import axios from "axios"; +import { TwitterSubmission } from "../types/twitter"; +import { useLiveUpdates } from "../contexts/LiveUpdateContext"; -const StatusBadge = ({ status }: { status: TwitterSubmission['status'] }) => { +const StatusBadge = ({ status }: { status: TwitterSubmission["status"] }) => { const className = `status-badge status-${status}`; return {status}; }; @@ -12,21 +12,24 @@ const SubmissionList = () => { const [submissions, setSubmissions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [filter, setFilter] = useState('all'); + const [filter, setFilter] = useState( + "all", + ); const { connected, lastUpdate } = useLiveUpdates(); const fetchSubmissions = async () => { try { setLoading(true); - const url = filter === 'all' - ? '/api/submissions' - : `/api/submissions?status=${filter}`; + const url = + filter === "all" + ? "/api/submissions" + : `/api/submissions?status=${filter}`; const response = await axios.get(url); setSubmissions(response.data); setError(null); } catch (err) { - setError('Failed to fetch submissions'); - console.error('Error fetching submissions:', err); + setError("Failed to fetch submissions"); + console.error("Error fetching submissions:", err); } finally { setLoading(false); } @@ -39,10 +42,11 @@ const SubmissionList = () => { // Handle live updates useEffect(() => { - if (lastUpdate?.type === 'update') { - const updatedSubmissions = filter === 'all' - ? lastUpdate.data - : lastUpdate.data.filter(s => s.status === filter); + if (lastUpdate?.type === "update") { + const updatedSubmissions = + filter === "all" + ? lastUpdate.data + : lastUpdate.data.filter((s) => s.status === filter); setSubmissions(updatedSubmissions); } }, [lastUpdate, filter]); @@ -67,7 +71,7 @@ const SubmissionList = () => { return (

{error}

- - ))} + {(["all", "pending", "approved", "rejected"] as const).map( + (status) => ( + + ), + )}
@@ -115,7 +123,7 @@ const SubmissionList = () => {
- { @{submission.username} ยท - {formatDate(submission.createdAt)} + + {formatDate(submission.createdAt)} +

{submission.content}

{submission.hashtags.map((tag) => ( - #{tag} + + #{tag} + ))}
@@ -138,13 +150,15 @@ const SubmissionList = () => { {submission.category && (

- Category: {submission.category} + Category:{" "} + {submission.category}

)} - + {submission.description && (

- Description: {submission.description} + Description:{" "} + {submission.description}

)} @@ -154,7 +168,8 @@ const SubmissionList = () => {
{submission.moderationHistory.map((history, index) => (
- {history.action} by {history.adminId} on{' '} + {history.action} by{" "} + {history.adminId} on{" "} {new Date(history.timestamp).toLocaleString()}
))} diff --git a/frontend/src/contexts/LiveUpdateContext.tsx b/frontend/src/contexts/LiveUpdateContext.tsx index 0676d0c..0c546ce 100644 --- a/frontend/src/contexts/LiveUpdateContext.tsx +++ b/frontend/src/contexts/LiveUpdateContext.tsx @@ -1,16 +1,27 @@ -import { createContext, useContext, useEffect, useState, ReactNode } from 'react'; -import { TwitterSubmission } from '../types/twitter'; +import { + createContext, + useContext, + useEffect, + useState, + ReactNode, +} from "react"; +import { TwitterSubmission } from "../types/twitter"; type LiveUpdateContextType = { connected: boolean; lastUpdate: { type: string; data: TwitterSubmission[] } | null; }; -const LiveUpdateContext = createContext(undefined); +const LiveUpdateContext = createContext( + undefined, +); export function LiveUpdateProvider({ children }: { children: ReactNode }) { const [connected, setConnected] = useState(false); - const [lastUpdate, setLastUpdate] = useState<{ type: string; data: TwitterSubmission[] } | null>(null); + const [lastUpdate, setLastUpdate] = useState<{ + type: string; + data: TwitterSubmission[]; + } | null>(null); useEffect(() => { let ws: WebSocket; @@ -18,34 +29,39 @@ export function LiveUpdateProvider({ children }: { children: ReactNode }) { const connect = () => { // Use the proxied WebSocket path - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsUrl = `${protocol}//${window.location.host}/ws`; + // In development, Vite runs on a different port than the backend + const isDev = import.meta.env.DEV; + const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; + const wsUrl = isDev + ? `ws://localhost:3000/ws` // Direct connection to backend in development + : `${protocol}//${window.location.host}/ws`; // Use relative path in production + ws = new WebSocket(wsUrl); ws.onopen = () => { - console.log('Live updates connected'); + console.log("Live updates connected"); setConnected(true); }; ws.onclose = () => { - console.log('Live updates disconnected'); + console.log("Live updates disconnected"); setConnected(false); // Try to reconnect after 3 seconds reconnectTimer = window.setTimeout(connect, 3000); }; ws.onerror = (error) => { - console.error('Live updates error:', error); + console.error("Live updates error:", error); }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); - if (data.type === 'update') { + if (data.type === "update") { setLastUpdate(data); } } catch (error) { - console.error('Error processing update:', error); + console.error("Error processing update:", error); } }; }; @@ -69,7 +85,7 @@ export function LiveUpdateProvider({ children }: { children: ReactNode }) { export function useLiveUpdates() { const context = useContext(LiveUpdateContext); if (context === undefined) { - throw new Error('useLiveUpdates must be used within a LiveUpdateProvider'); + throw new Error("useLiveUpdates must be used within a LiveUpdateProvider"); } return context; } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 964aeb4..27481e0 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById("root")!).render( , -) +); diff --git a/frontend/src/types/twitter.ts b/frontend/src/types/twitter.ts index 59abb36..c22be76 100644 --- a/frontend/src/types/twitter.ts +++ b/frontend/src/types/twitter.ts @@ -1 +1 @@ -export * from '../../../backend/src/types/twitter'; +export * from "../../../backend/src/types/twitter"; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index e9baf90..e78605b 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,13 +1,8 @@ /** @type {import('tailwindcss').Config} */ export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, - plugins: [ - require('@tailwindcss/typography'), - ], -} + plugins: [require("@tailwindcss/typography")], +}; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0ca537a..e9556cf 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,20 +1,20 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; // https://vite.dev/config/ export default defineConfig({ plugins: [react()], server: { proxy: { - '/api': 'http://localhost:3000', - '/ws': { - target: 'ws://localhost:3000', - ws: true - } - } + "/api": "http://localhost:3000", + "/ws": { + target: "ws://localhost:3000", + ws: true, + }, + }, }, build: { - outDir: 'dist', - emptyOutDir: true - } -}) + outDir: "dist", + emptyOutDir: true, + }, +}); diff --git a/package.json b/package.json index c2b150a..e158988 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,16 @@ "lint": "bunx turbo run lint", "deploy:init": "fly launch", "deploy:volumes": "fly volumes create sqlite --size 1 --region lax && fly volumes create cache --size 1 --region lax", - "deploy": "fly deploy" + "deploy": "fly deploy", + "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", + "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" }, "workspaces": [ "frontend", "backend" ], "devDependencies": { - "turbo": "latest" + "turbo": "latest", + "prettier": "^3.3.3" } }