From 466365bf05c56f108a8f4e3a7fcd698e0795f1b8 Mon Sep 17 00:00:00 2001 From: 0xWulf <0xWulf@proton.me> Date: Sat, 31 May 2025 17:38:07 +0800 Subject: [PATCH 01/16] add: Logo.tsx --- client/src/components/Logo.tsx | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 client/src/components/Logo.tsx diff --git a/client/src/components/Logo.tsx b/client/src/components/Logo.tsx new file mode 100644 index 0000000..f5abf20 --- /dev/null +++ b/client/src/components/Logo.tsx @@ -0,0 +1,46 @@ +// src/components/Logo.tsx +import React from 'react'; + +interface LogoProps { + size?: number; + className?: string; +} + +export const CodePatchworkLogo: React.FC = ({ + size = 32, + className = "" +}) => { + return ( + + + + + + + + {"{}"} + {"<>"} + // + [] + JS + PY + 01 + .PY + + + ); +}; From 85551df3f84336d664072d8870ab82f28aea7222 Mon Sep 17 00:00:00 2001 From: 0xWulf <0xWulf@proton.me> Date: Sat, 31 May 2025 17:39:27 +0800 Subject: [PATCH 02/16] fix :wrapping the Unicode character in JSX brackets for consistency, but your version should work fine. --- client/src/components/Logo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Logo.tsx b/client/src/components/Logo.tsx index f5abf20..022ceae 100644 --- a/client/src/components/Logo.tsx +++ b/client/src/components/Logo.tsx @@ -40,7 +40,7 @@ export const CodePatchworkLogo: React.FC = ({ PY 01 .PY - + {"⎇"} ); }; From ba6f2b20017d4507fb8e20e8953d9890304bacbb Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 09:51:14 +0000 Subject: [PATCH 03/16] feat: Add custom CodePatchwork logo as favicon This commit introduces the CodePatchwork logo as the website's favicon. The following changes were made: - Extracted the SVG logo from the `CodePatchworkLogo` React component. - Prepared and cleaned the SVG for use as a favicon. - Generated `favicon.svg` directly from the cleaned SVG. - Generated PNG favicons in various sizes: - `favicon-16x16.png` - `favicon-32x32.png` - `apple-touch-icon.png` (180x180) - Created `favicon.ico` from the 16x16 and 32x32 PNGs. - All favicon files have been added to the `public/` directory. - Updated `client/index.html` to include the necessary `` tags for all new favicons, ensuring they are referenced correctly. This replaces the generic browser default favicon with the branded CodePatchwork logo, enhancing brand consistency. --- client/index.html | 3 +++ public/apple-touch-icon.png | Bin 0 -> 955 bytes public/favicon-16x16.png | Bin 0 -> 117 bytes public/favicon-32x32.png | Bin 0 -> 158 bytes public/favicon.ico | Bin 0 -> 5430 bytes public/favicon.svg | 5 +++++ 6 files changed, 8 insertions(+) create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon-16x16.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.ico create mode 100644 public/favicon.svg diff --git a/client/index.html b/client/index.html index 78e48db..7bd65a5 100644 --- a/client/index.html +++ b/client/index.html @@ -11,6 +11,9 @@ + + +
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..13e18acc83dae236e8cbc8d6ef98fc0d3717b433 GIT binary patch literal 955 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD4M^IaWitX&Y)RhkE)4%caKYZ?lNlJ83p`yM zLn`LHy=k~Q^{Hgr!{0gF%TG*jb97F!))d{?8<81w#371vrhLN8c$FKkIJ|VWPPuq6 z%u$G0$tB59jI-nTBc3N5R$awyw$C5#deFL8Jo|9;ogA^()-T&*SJuVvjj)Zi4YMtp z{`+Cw&qcrX?25d*;$Pg~@5}%F2#8((FLbWdXNGgjuf|(ve_dA>xc47}PR<$sncG*z zmz_Jg^L+T~^403iryqxX%_-ge@Bj4L6>+|2V(k1w*Ox4R+WszPPu$S@FU0v;!sSobeM8#T%)O(zXsq+sxFK<!%bGUFW($tRr~WI(jrE!DXnY4Or?xv9J4e>nS(bRidOjb?`4UZaM}6m z{N#K_@h9ERUvF;Q-oX9o=EAD~H=+-)?yOvqCj*LTbH7#ESKY7X_wC`i(_J5SU2fIB zb^F%*E4fqqVBVgXyH-0tKMmL$_V;7O?*r$*MSXq#>dTWmM^|69z8dTv$yqm#e^vVG z^{;lTw{Bm3zckD`hAltre5v{~`Jxs7F5Ptgw$hOQ!JoxdE3N*w1l})yIH!HJxK;JK zx@A>ss=nxbKm7O8`&XY|?S0kz>g}ug`{j?ff6CGe3vFzopr0KPxK AH2?qr literal 0 HcmV?d00001 diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..ace1d0c3ce701cad12507b967fa727e1440f8a4f GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt^yo-U3d z7QM*{2?-Xb4jlL}-=}7V;3)-ZgTO_VR~iDD-FO{LA8@e#;%8>y3=xvscc$w&P#1%z LtDnm{r-UW|3*jQl literal 0 HcmV?d00001 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..9c2195899a96945972478cc99d65444a9076d038 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7l!{JxM1({$v}}{PZ!6K zh}O5)H}W1(;BmR=|Llarf;ame&d6PuAzJX+e^bfbz1JAz4H`>-GD+y3_V!)0beWF- z(;WtcxGyxH_J#i|)$X6Qn_t{PyZf)q`i!Gj zbn8c_S>w4|LusXd&EdkFb)?+>to99lt*Bav0Wly3#DEwO17bi7hygJm2L8=JjM>*S SW%FlC-y+PRc5a*fquK!hgZfMW literal 0 HcmV?d00001 diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..8225469 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,5 @@ +{}<>//[]JSPY01.PY From b5ca519895cc6ac97db5ba474f26a139e6ea6a0a Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 11:48:11 +0000 Subject: [PATCH 04/16] fix: Correct SVG class attributes and regenerate favicons This commit addresses an issue where favicons were not displaying correctly. The root cause was identified as the use of `className` (a React-specific attribute) instead of `class` within the `public/favicon.svg` file. Changes: - Modified `public/favicon.svg` to replace all `className=` attributes with `class=`. - Re-generated all PNG favicons (`favicon-16x16.png`, `favicon-32x32.png`, `apple-touch-icon.png`) from the corrected `public/favicon.svg` to ensure they reflect the valid SVG structure. - Re-created `public/favicon.ico` using the updated PNGs. The links in `client/index.html` were already correct and did not require changes. This change should allow browsers to correctly render the SVG favicon and, by extension, ensure the derived PNGs/ICO are accurate. --- public/apple-touch-icon.png | Bin 955 -> 6865 bytes public/favicon-16x16.png | Bin 117 -> 436 bytes public/favicon-32x32.png | Bin 158 -> 733 bytes public/favicon.ico | Bin 5430 -> 5430 bytes public/favicon.svg | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index 13e18acc83dae236e8cbc8d6ef98fc0d3717b433..c9dc5b77fb27bb77054b770926e98a2aeb0d6470 100644 GIT binary patch literal 6865 zcmY*e1y~zRx27#noKoDO6nA$h?i6>I;2NZ*NK0{d4<%5v5Q+v*@!~GUg1Z*?8@~U$ z|8wuNyV=Q3axyb}a$ea;4Rr-<3^EKPBqVGlMOiJxx%TNmLq)ub525lPPN-HY3bII# zPg`DVaWWDTS(uWnl+K5o1JEabqAAcLd}~mwQS9wXQo?6>h-YyWh9lU_sLpIuy0qVM zYS*aQy8_~5>SaDoG`~B2U)Q8%TD8P7HT^Kkq{p^|w85Z^^h}BdBl1~Sh{|vA5nkX$ zywcxwCF!5XgIJluM?uRUbNez!GOq_SkFIlh?U2JQMtNCeru59&ETXhl`H=6C*C`!u z58th)&eO0b&(h;wv3KLYkrR~ROP`g4T^RRkg7aQ?6H7--F@2IS{DbMcKtm=nKfQlE z#76&eL+|n3)byPe4aTz&cf%_A36$r5U#)*3i+(kuZG3v2r-1w5uON3}0zhv*sINW< z=Z+7|X> z-!gerQO2|Vb9F#por_zg@5{%!QwB^FO%z1ym9X5KE(b&d!>}5;F`yLA zBPNr^*1=@D1I5DCELO^c!Fl+ybn=#k(9bT6Bjl4Ykk_+n*YjmQvxpJee!ph#}8yu%avfU?_NsX2f}PWgq6&bm)%I)or1Mh z`QDJEvMRbQOaav}+Z9n1y{n&-DAXGel?KP0@vTSXTyjhyKIiINHZC**6<7y`GPNuPDC=>F6JM zIA|^HVy$jix!cMo?k5lB0|_4fg7}nw5NJnd;WV0tp$FyUguOaGfKmB1({=00n2S!+ zaI%z?Ym5D4QI~6-kcM&{;b*5dTM+=zA5QmBd+pF6Bo4>hWW?DH@!W5JZ|?okv-o4` z1~+XC2v#aW$1RVt_8p_TKekB@rg9(obi!eL@)Y-SZzZQM9cnZPpFUAdqtr>E^kU&} zgW2qINx`UGS^mbgi=(9HGBx^#^x-}(g*?v%QLG$&`_t0Uy_($G-iV2qafZmCg<<2+ z-@h{ERRKIGk9Yii7c6pPdIN;)klsApqBnDtxzj$ec@Ts z^}MqLM6C8oN*rZEfvkMVrKV)EwP8S4W=qf))!KZ8tvmZgW!u;x|LdR&^dw0Wf@jME zA{oux!_mh3(;8*$YD8>zgMbw~fft_0>G27wcm{t`hwcCIRM+V%~m;=^~+p6v@)4@C; zVe`KS?MZ_5^W}fm4pH`^z?GEN$Cbrde1`5@`3C9Gd?LM+`8!WT3R;UEQdBr)?oD=h z)vi*^s$^m9;EP!GVQ;Toi*`bXyX$H+I3-8lzI0#_I-+qs;QNNH;Z1B}V{lW(>N{&k zYX|EMiov>(;@Ux?H(|+tgl0>wI-4NIi_If^Kb^0>;VzDtHNN(2Y3(fHGCr>Ol4J!Pj8F0$MC9QJ*!NcW!uY#`hWkuxJS#Lth01BeOM)Y`d%$Zlm?GH{)qGX zk8HPSVN^QQu-qOp@N+4cx$~`l9N%_oB83x7&kE{8r5(C(k0%S%HUePI#0Zci&|L*l z8<^t6AXK}U>Wkw_vgGs!EWLzb|fl5MP(0-{%GuG;6Nek?Cena!s}+#Zuu|#H~pASb8HY*&5Aha55#`5Fg(} zq%v}s#B|Blxv;@8Fs4a}`H0*r^hel|{*li5Dj3!6=x3>8$cxz8xR~?)S2hYc3#W}Q zazX}{Ze0C2ah}z2%N^)9jPZ5t@T6`LLCBiye+*4pqJI5^5mh8inO|RX;3~%avEI~F z^~6!>lGI?7;!}g;_9~|{18ladYmMH}LA}`RoEW{6PL-%^|$swm6I3^@wY$pk8vfye#k2L{*)v&%SI2ya})LtsRdp!P@{ zp6rym;=mcABcjYRV(ON?Q(JmU+9^0*pq4=+gk}AwdM*x?+OZ_jPxVGJ+oGrbvtLmR z8rumn_)PzdKqoBiTcvawF`|ptW=DB_m>&UhDaU6CNCj6P6$iRFuzCl8v8#=*hPovl zbYO)aU|`g)_|Nki1iTtne?PBb5lg+g+B24nz6ls6H=0lLI0;bV+~(F3-(eG2iV?pH z{vL`iXN$gB{l`Tovx_5u%W!-3WzO~BkOAZSUMjKD^O`&V)MPu?u}srbEL>oK*#owkGIv7w6Bkl*4C30#LoY!PjRW}zFgRn;c-bxK!!Oq;rUcGh$~5u z>&?N%Sf6U0k2j%o!sHKu@%hw5|CU#j@%k^{IG9r5(#Ni^k?wql0Gdua-jA7>|2h0n zSLw8V?0tZq|hifb(4%+h60b;*kE)9!5;=`ql%)<%DeW&e5=G z`zFr0(X4lPWwjtvuCmjW{m=Or@&)6cZl1D`|EvOC;{QgKYk7v}c5EEx+qK)NYc372 zKdmFpaC_)*BhAITo~O)k3%A>4YRK5&qR*KWy!LbWNs!Hwe4EF(r4r)%*^AQ|uT?b&~AH>y5O(=R(-2Ky*A-v7LMt2ZiwIUpqHdfO|zZk-eVaj3}49Q#h zkkgmSTPGy|M1ek?kmGJEIoCQ!#N%ED{TLVVhry&yHhr?;ffkxmB@F>BeZFBz2yR6@ z;`C<8e}eEAHKPJaGUEs6;AT;1Rm$#ZmsIqCGs~U`32;JVy6fA}aok3KfhgmYI{^TWdEF|M7y; zBp}aHAA8?PV;z8YV~1W8&qFe)?SA0I{qRXQ=JaJD_s`m?ceig9?I5SN@E6dlhnTbVDMd28nQizedQGflZWX>1SyN0z zwidNkt@T5}_FU$CJ9|!g{&;pyXd)_0Xlo=^Olv>mG-mBBqR6(Df9;(XiJf|{&fCWY z3Y>zycb^-z6YTk8mU`2`V-j|eh^X94KE*h^zm(59300tWh95g} z0(d}8;x)AD4ykV>F=pcUGbP3?-o+spPxm9lyLhb<`v5PNNbJ#Lwf^>@&Or>Q6tdSt zwl24b`L*R8MuU5VHNA>3DCjc-=3x-L^i+*e$yIm?Jt*)}DkqD<*YAF@nfLtm9Z29${mC}r zoc+DiMoN#qymaJcM041Fc4!=;nmi@N%RA>p8&gq=sIE7pcv zRebG^FR0y!3^=*Rk8|DWGw1DD9-hG7wTfaji>z{iowm}V6zP1XWw1ZnEK%C zfRhS7XipY_@cX9d@Uv{X)m$y1eKl6+6}J1_USd+5eCe9GD55NIgdfeSpp3eXW|o=O z6W;7X=CYNxD9BP0%WS6Z;p~7bfjO?7gDKX7o(i6aJkL@Ntn28|;&N$dpSi(U6=t~=6BI#gX-}nUk)M){Gil!yijXDwcV4hQ8#s8qorZMe5B4a-<1}asR<`|PU7Y_$6M^r_4XAp2UlhVAvfhEQpYVjI76FG< zcF9jdc>f0NWCQT@sS+%ga=+&_SB8i;Vb>27gZv}^a1z`9Qict1-)|{7o;$r2N>bsH z1a`iHQv`;9WpLE;5Rmd;45D*e++!2KfBgp2&Z_O!7!fXtl(|WGC;F<9S0exw8YnP5 zf)L=2_s+y|y(0HL(gCsSyk$dbA}xC8}F6%tq4Zc?xcJqt%%>kPz}={vdnTeL8S%2@rsh* zF?3KiF^wQ>`o@qjhCyOyTJ~9$-e1({yfA$=2^A`9T#b({h0Gp-Sogx1$AjIo|9O3Q6WgN*KaU7!4v!VHygbWMpi^iU^*f)ot77GqR(mKSM?GE z^O2YvB^$ZgM2aK`hacLz^W%c3Lp47pWuTC=5KO6s55Ky2QU`hM!BI_z-;~58RNjJG znuPW%-b3^s*|gS^tH^#w9FGr$}`vtO_)4P6}igZIr zj4(2U$JTGHW2VESch`=8Y|*0QEy5U|UoBrD$l)WjM<(QdZd{-Ho_!rPNai^J(sw+-TWGBsxGvYqFg zYD&jT`vfuQi_h^*#OLzTU$T0YF1=WUD~a~wu=wb)V)&tjoxL^@ll8PZ;wx&I?0SUM zW%8ILQWM^t&b+OO21N&0fo-(FU3$kq2tDG?TA%@WQ=G13c04;mTOI0dKJ)9~q%!$r z!+}+1_%~MWv#A2mONFiSng(CPoOyU;Rm*}r##OOqWVmt=5Yoq9oV8z~)za9S4cEqP zN1#sNPnsUNM_A-Hc5n4%gm%`(cw5Ka?WQjbUvy8BaxHp?Ec)1PfX0*=U{!Q2AJj_af}V2l9KB^EHF%eJIdMev84iGagcP)gDZu z-%3_cR&|@(P}$!Pid-9O7M93`4FMLZvZ?Ue<9kU>#B_KB-fJv=|6uQZ8PK^r+ZKE~ z5=rw}A!^M8LG>7?g^ArRCbTRcwaA3vH7V0cQMjdGf^@E*}Hv#ZQSM|*dkcbqO&ioDAWY`yzH zL8^c~HWebv6o}Ye81vi3&k7t|%n=<~;yz9}Ed~7m--^{UD(ik5>-;qb->zb zO{{gPhyxPgJ+?g!y{_Px7LOlOTT?B!c*6B~`2Mvm>m)$XO~w>oNBT3H4-p|Gne6&^1vGxy{#h zAeu(&;4jY~P8UhY1Nw&>lEQOqhOMN7`Ysj7WA^E`$qBK3=WxsU6*EeE0#uw zoW7tOSFf9Sx^535|12T9bP&_^B2)zDZ~?)cPqVeYb$fJ=4h*;e*dn>tuxj`(8^F@s zr>!Knb6AhLIh>@K*nh5os6|3%n12=c+477wCS~A#;CVa?Z zvl~Q&`}B@&Dc!riLQCZ)k7Q_RYM_X6Dm8iFomPvj6hLb;p)e!}z(XIyctC)q?9x5x zA3A^lUg{_D_k_Sv8ti;@WIsUamWpP?Y-G41M||jrZ#t`dDYUy~Z`Yn{b75iozy5)M zo_M6@9pe+?S9#)W&TS#;JTo{7ym5G*kfvk+i1@uo?fo}t!~(wz;cNWI_t2pY7r%R+ zf|tuOPsg!;b-4c-zw8Sk!dCcS>JqwckQs3FEFWREAMrkgE}4y*g)X|0n&rR!IpRiW zX33Eqd-AaMWYPn7WHPA5o}Bg4lwTY$U3)%et8`?|eJDmO10fo%o=S>1+>JKZGK(fY z7Uh??lzdz!%1CQ_E+?vZ0Z&QAAB>!3C`Ih%Z_UCYkht_=gA^al+i zTpO$0ym5C&P|YsXvIa7usehSV{6s8ur~j73AYuF!dnbR$YfRPVoos4&lKI46)A7;E zUg-MFe#u~y3EIhvyg^_n2VI%Z8=Nr41CFwOyq7S9bjB<}RN%V#pz*cpqxc47}PR<$sncG*z zmz_Jg^L+T~^403iryqxX%_-ge@Bj4L6>+|2V(k1w*Ox4R+WszPPu$S@FU0v;!sSobeM8#T%)O(zXsq+sxFK<!%bGUFW($tRr~WI(jrE!DXnY4Or?xv9J4e>nS(bRidOjb?`4UZaM}6m z{N#K_@h9ERUvF;Q-oX9o=EAD~H=+-)?yOvqCj*LTbH7#ESKY7X_wC`i(_J5SU2fIB zb^F%*E4fqqVBVgXyH-0tKMmL$_V;7O?*r$*MSXq#>dTWmM^|69z8dTv$yqm#e^vVG z^{;lTw{Bm3zckD`hAltre5v{~`Jxs7F5Ptgw$hOQ!JoxdE3N*w1l})yIH!HJxK;JK zx@A>ss=nxbKm7O8`&XY|?S0kz>g}ug`{j?ff6CGe3vFzopr0KPxK AH2?qr diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png index ace1d0c3ce701cad12507b967fa727e1440f8a4f..4af04a7b06dd2194b04810c8a77961f586edea9a 100644 GIT binary patch delta 392 zcmV;30eAj&v;&Yee*tMpL_t(Ijg6DtO2aS|fWM@vqnm7QIE4v1K~Tg8Q1No$6L{lu z`7(lcPVgDLQAA|}nVVX>u0Qc2?bh0j83=^$l)IDx3U6qF(lWP51<`~8kAnQ3WUW%O6cFfH1X$yOG!G1k&{Crn(d1&QWr!BcSuqfMNCJ~_T#`sZsfJwT3%!p0c%~OVv zk=S$LW1LtIfBmAZd2F%7peQl$L3xWMPGzO#{*i_97E7#_1SeaPotns%d$ca-v?a~5 z$hC0XlB`&13&2mHdHn)EOdKS}-#yLCH;l%5${LQK?`k5QIUtPT`7`ru6XWLD>Kq$* m$$v?=YUuvU_&g@K{ROTmjd-Q6J_`T<002ovPDHLk0$_r(LA$L0 delta 70 zcmdnOTspzjfkDgD#WBR9H#s38!Q#|`10Ux5)XWe(r66q(xTx|6W4%T1% Z%nY0%LUQ}gbRA~^0#8>zmvv4FO#qU_7_0yQ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png index 9c2195899a96945972478cc99d65444a9076d038..7c1449f286fe65984efb22f961e2c90005d39619 100644 GIT binary patch delta 692 zcmV;l0!#g#0o?_VHGcwJ82tmHHeK|`GX)@L9 z?RT>?yEC&ZL(f-li>oGqH~}KLIdt#wAfzc)LmI6nOkDM1r+>u(u`J%dW%)pL<9o^D{*sF12I_r{(3E2%VYE} zhv?sROAdkTbbr>xGT}o;;}=OiTd)0EM_OG4+ftJQ4W-3Z(6j@)R0ZK3@Mm_bSW|Ly zVA*KP-ukhDF6B*hF(1hM(+i8CY+~7jPC|-#usXwq)daieM^NKHDkgM9guZt1PW-Pb z7)vV3u4a75JLS)8J1RW5Xz=io!IgM0`*h6Y{)ow=%YP=Pdc1+vI8Z2WbvDPB8UAfO z-6JghhA+!7+#7svY*ygatV3=?^Lf#0*wcyuTn*iA!lJKd`?qx}<1*FgsD12{w-o?h z&NwybeiiKB1;)YAyN|^w0fH=lqmizb=Fx54fy}n9zQ2b^`;IonDG~lXu=W$(H4rMQ zX4pMH0)LMKrh;n;MZ|!k2fQ7tUS?l7tVqR#WS5YNNgxYwwr|U}XU*!tNWWq{smPaL zxL5ePQq?%>M0Ime$d?rn1@F=>paWRNxjx0hnr6AG&3YV|U(sX=nnGFA5oz4t1vuFw ze9UUz{BXFQP<+a{byid;2b0x=hFa=?(@)CKe6>1&0PO!=fQ_hdhJw>9^niu>WG)QtlYD=XXdM P0}yz+`njxgN@xNAa1Si+ diff --git a/public/favicon.ico b/public/favicon.ico index b3f986939456c61bb19284ec8f5dcc6f59c52b90..da914d4a824c9103a89a622d6421e86420faffb9 100644 GIT binary patch literal 5430 zcmcgwS8EkP6kf$2kbjUHjRm`c1r!U4*cA)dMG@2&1sf7<5RIbP&?MH_HTEvXu89o` zKKLXem+IaQn>{aH80O43-2TL2ngE_EU%dX^@=6qOKoqJ*XvHfdrp}!Af zeppiu>hv%O>js%Te(wJ3e_}AGV|@`T1IEce7R+=sq21xPQ#SZjTEDMU{yk{Ie4Jz5LuaQB3M>TnO#w6no{x%x1Q z#hS4m6#M(mLYy?UIc(c|V4L&jG4(#op0{mZu0AaG!F!SJDRps@**|No&W{0V#A7VP z>tq^Yu|L{$|I@uq80V^v_5o@(M)$p{8;JLRIs3n~*CE@Ns}H09zFQBZW2!SbY$LEy zJu*KkJkC!aC>@9KTHIXS@t3MH-dCG_MsBmJ&W+6yn{xTEIeUBox7poiu`yr!V)HgG z;o5#_c1BIjwUKbsV%l>8O-0wrjzRyksId~%xzUhs+Mc-2lA4xbO&kyU)6uv9j<2uIfc(efrshUZHzasIO{2H_G0$y~f3*{RrpA09 zSKnL!`PaMgx!L+Gu;a!y)((ARuBq4R?5KD>n)m6iuS~~h>xkAGg*~t1l`4SDW)jbZb%keR$AsGQqRgXFGbn#k>PVkJl%-uBPfpFNogkgXmq2dBRoRBbG><<a4nvfXe#0I`fc^D{%L#(H*Fnz&ehbOb8QKi zawWaUC(UlE$XTb5YUUZ3sg*+x*Oh5~(KV2t_#O>4V?zB<#i5bZQB?Mq*t2I|7t_*;_A z&tey7YSm`fU&77${^@G!#tcxGyxH_J#i|)$X6Qn_t{PyZf)q`i!Gj zbn8c_S>w4|LusXd&EdkFb)?+>to99lt*Bav0Wly3#DEwO17bi7hygJm2L8=JjM>*S SW%FlC-y+PRc5a*fquK!hgZfMW diff --git a/public/favicon.svg b/public/favicon.svg index 8225469..02afd08 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -2,4 +2,4 @@ .bg { fill: hsl(207, 90%, 54%); } .stitch { stroke: hsl(240, 3.7%, 15.9%); stroke-width: 2; stroke-dasharray: 6, 6; fill: none; } .symbol { fill: hsl(0, 0%, 98%); font-family: 'Courier New', monospace; font-weight: bold; font-size: 24px; dominant-baseline: middle; text-anchor: middle; } - {}<>//[]JSPY01.PY + {}<>//[]JSPY01.PY From 171bf3b1e0146569a6a6fe37794d6eb81a997f1a Mon Sep 17 00:00:00 2001 From: 0xWulf <0xWulf@proton.me> Date: Sat, 31 May 2025 20:08:39 +0800 Subject: [PATCH 05/16] fix: fixed the favicon errors. --- client/index.html | 10 +++++++--- public/favicon.svg | 32 +++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/client/index.html b/client/index.html index 7bd65a5..48907d9 100644 --- a/client/index.html +++ b/client/index.html @@ -11,9 +11,13 @@ - - - + + + + + + +
diff --git a/public/favicon.svg b/public/favicon.svg index 02afd08..ee0fd08 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,5 +1,27 @@ -{}<>//[]JSPY01.PY + + + + + + + + + + + + + + {} + <> + // + [] + JS + PY + 01 + .PY + + From 9013c4754a2a2c9e19d1769976bc7b2b3925b182 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 12:37:29 +0000 Subject: [PATCH 06/16] Fix: Configure Vite to correctly serve favicons The favicon was not displaying because Vite's `root` was set to `client/`, causing it to look for the `public/` directory within `client/` by default. This commit updates `vite.config.ts` to explicitly set `publicDir` to point to the `public/` directory at the project root. This ensures that Vite can find and serve the favicon files and other static assets correctly. --- vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vite.config.ts b/vite.config.ts index 0ea964d..8ca86a5 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -44,6 +44,7 @@ export default defineConfig(async () => { outDir: path.resolve(import.meta.dirname, "dist/public"), emptyOutDir: true, }, + publicDir: path.resolve(import.meta.dirname, "public"), /* ---------------------------------------------------------- * Env handling From b56d5a0a0f00562963fdf11b462113606ae9fd90 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 01:57:01 +0000 Subject: [PATCH 07/16] feat: Enhance README.md for CodePatchwork This commit significantly revamps the README.md to improve engagement and prominently feature the live demo. Key changes include: - Added a prominent "Try CodePatchwork Live!" section with a large call-to-action button and badge linking to https://www.codepatchwork.com. - Enhanced the header with project badges (Stars, License, Demo), a new tagline, and a more descriptive introduction. - Incorporated a Table of Contents for easier navigation. - Added a "Quick Start" section directing users to the live demo or minimal local setup. - Reorganized the "Features" section into categories (Visual Experience, Organization & Discovery, Collaboration & Sharing) for better scannability. - Added a "Screenshots" section with placeholders for visual examples. - Appended a new footer with a final call-to-action to try the demo and links to star the repo or report issues. - Ensured all existing content is preserved and integrated into the new structure. - Multiple mentions of the live demo URL are now present. The goal of these changes is to make the README more inviting, informative, and to drive users to the live demo. --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 2556d8c..bbfdbb9 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,72 @@ # CodePatchwork 🧩 +
-A visual code snippet manager that combines the visual appeal of Pinterest with the functionality of GitHub Gists. CodePatchwork transforms how developers manage code snippets by replacing scattered text files and notes with a visually appealing, searchable repository. +[![GitHub stars](https://img.shields.io/github/stars/hexawulf/CodePatchwork?style=social)](https://github.com/hexawulf/CodePatchwork/stargazers) +[![Live Demo](https://img.shields.io/badge/🚀_Demo-Live-success?style=flat-square)](https://www.codepatchwork.com) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) + +**Transform your scattered code snippets into a beautiful, searchable visual library** + +*A visual code snippet manager that combines the visual appeal of Pinterest with the functionality of GitHub Gists.* ![CodePatchwork Banner](codepatchwork-banner.png) +
+ +## 🌟 **Try CodePatchwork Live!** + +
+ +### **👉 [🚀 EXPERIENCE THE DEMO](https://www.codepatchwork.com) 👈** + +*See CodePatchwork in action - no installation required!* + +[![Live Demo](https://img.shields.io/badge/🚀_Live_Demo-CodePatchwork.com-blue?style=for-the-badge&logoColor=white)](https://www.codepatchwork.com) + +
+ +--- + +## 📋 Table of Contents +- [🌟 Live Demo](#-try-codepatchwork-live) +- [✨ Features](#-features) +- [🚀 Getting Started](#-getting-started) +- [🔧 Usage](#-usage) +- [🛠️ Technologies](#️-technologies-used) +- [🤝 Contributing](#-contributing) + +## ⚡ Quick Start + +Want to jump right in? **[Try the live demo](https://www.codepatchwork.com)** - no installation needed! + +For local development, you'll need Node.js 18+ and PostgreSQL. See detailed setup below ⬇️ + ## ✨ Features -- **Visual Organization**: Manage code snippets with a Pinterest-style visual interface -- **Syntax Highlighting**: Automatic code highlighting for 100+ programming languages -- **Powerful Search & Filtering**: Find snippets by language, tags, or full-text search -- **Collections**: Organize snippets into custom collections for better categorization -- **Tags & Metadata**: Add tags and descriptions to make snippets more discoverable -- **Authentication**: Secure sign-in with Google OAuth or email/password -- **Responsive Design**: Works on desktop, tablet, and mobile devices -- **Sharing**: Share snippets publicly with customizable links -- **Import/Export**: Easily backup or migrate your snippets -- **Dark/Light Themes**: Choose your preferred visual theme for better readability -- **Comment System**: Collaborate and discuss code with other users +### 🎨 **Visual Experience** +- **Pinterest-Style Interface** - Visually appealing snippet organization +- **Syntax Highlighting** - Beautiful code display for 100+ languages +- **Dark/Light Themes** - Customizable visual experience +- **Responsive Design** - Perfect on desktop, tablet, and mobile + +### 🔍 **Organization & Discovery** +- **Smart Search & Filtering** - Find snippets by language, tags, or content +- **Custom Collections** - Organize snippets into themed groups +- **Tags & Metadata** - Rich categorization and discovery +- **Import/Export** - Easy backup and migration + +### 🤝 **Collaboration & Sharing** +- **Public Sharing** - Share snippets with customizable links +- **Comment System** - Collaborate and discuss code +- **Secure Authentication** - Google OAuth or email/password + +## 📸 Screenshots + +![Main Interface](screenshots/main-interface.png) +*Beautiful Pinterest-style code snippet organization* + +![Dark Mode](screenshots/dark-mode.png) +*Elegant dark theme for comfortable coding* ## 🚀 Getting Started @@ -154,6 +204,18 @@ Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for det For questions or support, please open an issue on the GitHub repository. +## 🌟 **Ready to Transform Your Code Snippets?** + +
+ +### **[🚀 Try CodePatchwork Now](https://www.codepatchwork.com)** + +[![Star this repo](https://img.shields.io/badge/⭐_Star_this_repo-black?style=for-the-badge&logo=github)](https://github.com/hexawulf/CodePatchwork) +[![Report Bug](https://img.shields.io/badge/🐛_Report_Bug-red?style=for-the-badge)](https://github.com/hexawulf/CodePatchwork/issues) +[![Request Feature](https://img.shields.io/badge/💡_Request_Feature-blue?style=for-the-badge)](https://github.com/hexawulf/CodePatchwork/issues) + +
+ --- Made with ❤️ by [hexawulf](https://github.com/hexawulf) From dd15ace09fe981e4423aec179fcd7106af50d574 Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Mon, 23 Jun 2025 11:22:21 +0900 Subject: [PATCH 08/16] feat: add winston logger --- server/index.ts | 59 ++++++++++++++++++++++++------------------------ server/routes.ts | 5 ++-- src/logger.js | 19 ++++++++++++++++ 3 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 src/logger.js diff --git a/server/index.ts b/server/index.ts index cc36b70..093549d 100644 --- a/server/index.ts +++ b/server/index.ts @@ -12,6 +12,7 @@ import camelCase from "camelcase"; import helmet from "helmet"; import { registerRoutes } from "./routes"; import { setupVite, serveStatic, log } from "./vite"; +import logger from "../src/logger.js"; /* ────────────────────────────────────────────────────────────────── */ /* 0. Verify & load your service-account JSON */ @@ -20,10 +21,10 @@ const svcPath = path.resolve( process.cwd(), process.env.GOOGLE_APPLICATION_CREDENTIALS! ); -console.log("→ SERVICE ACCOUNT path:", svcPath); -console.log("→ Exists on disk? ", fs.existsSync(svcPath)); +logger.info(`→ SERVICE ACCOUNT path: ${svcPath}`); +logger.info(`→ Exists on disk? ${fs.existsSync(svcPath)}`); if (!fs.existsSync(svcPath)) { - console.error("❌ service account JSON not found. Aborting."); + logger.error("❌ service account JSON not found. Aborting."); process.exit(1); } @@ -91,7 +92,7 @@ app.use( /* 4. CRITICAL FIX: JSON-only middleware for ALL API routes */ /* ────────────────────────────────────────────────────────────────── */ app.use('/api', (req, res, next) => { - console.log(`[API] ${req.method} ${req.path} - Request received`); + logger.info(`[API] ${req.method} ${req.path} - Request received`); // Force Content-Type to application/json for all API responses res.setHeader('Content-Type', 'application/json'); @@ -104,7 +105,7 @@ app.use('/api', (req, res, next) => { res.send = function(data: any) { // If Express tries to send HTML (like error pages), convert to JSON if (typeof data === 'string' && (data.includes(''))) { - console.log(`[API] 🚨 Converting HTML response to JSON for ${req.method} ${req.path}`); + logger.info(`[API] 🚨 Converting HTML response to JSON for ${req.method} ${req.path}`); this.setHeader('Content-Type', 'application/json'); return originalSend.call(this, JSON.stringify({ message: "API endpoint error", @@ -125,7 +126,7 @@ app.use('/api', (req, res, next) => { const newSend = result.send; result.send = function(data: any) { if (typeof data === 'string' && (data.includes(''))) { - console.log(`[API] 🚨 Converting status ${statusCode} HTML to JSON for ${req.method} ${req.path}`); + logger.info(`[API] 🚨 Converting status ${statusCode} HTML to JSON for ${req.method} ${req.path}`); this.setHeader('Content-Type', 'application/json'); return originalSend.call(this, JSON.stringify({ message: "API Error", @@ -203,15 +204,15 @@ app.use((req, res, next) => { /* 7. Route registration */ /* ────────────────────────────────────────────────────────────────── */ (async () => { - console.log("🔧 Starting route registration..."); + logger.info("🔧 Starting route registration..."); const server = await registerRoutes(app); - console.log("✅ Route registration complete"); + logger.info("✅ Route registration complete"); /* ──────────────────────────────────────────────────────────────── */ /* 8. 404 Handler for unmatched API routes - MUST BE BEFORE GLOBAL */ /* ──────────────────────────────────────────────────────────────── */ app.use('/api/*', (req, res) => { - console.log(`[404] API route not found: ${req.method} ${req.path}`); + logger.info(`[404] API route not found: ${req.method} ${req.path}`); res.status(404).json({ message: "API endpoint not found", path: req.path, @@ -237,7 +238,7 @@ app.use((req, res, next) => { /* 9. Enhanced global error handler - MUST BE AFTER 404 HANDLER */ /* ──────────────────────────────────────────────────────────────── */ app.use((err: any, req: Request, res: Response, next: NextFunction) => { - console.error(`[💥 GLOBAL ERROR] ${req.method} ${req.path}:`, err.stack || err); + logger.error(`[💥 GLOBAL ERROR] ${req.method} ${req.path}:`, err.stack || err); // Ensure we don't send if headers already sent if (!res.headersSent) { @@ -277,41 +278,41 @@ app.use((req, res, next) => { log(`🚀 Serving on port ${port}`); log(`📡 API available at http://localhost:${port}/api/`); log(`🧪 Test API at http://localhost:${port}/api/test`); - console.log("---"); - console.log("🔧 API Endpoints registered:"); - console.log(" GET /api/test"); - console.log(" GET /api/snippets"); - console.log(" GET /api/snippets/:id"); - console.log(" POST /api/snippets"); - console.log(" PUT /api/snippets/:id"); - console.log(" DELETE /api/snippets/:id"); - console.log(" POST /api/snippets/:id/favorite"); - console.log(" GET /api/languages"); - console.log(" GET /api/tags"); - console.log(" POST /api/auth/user"); - console.log(" GET /api/auth/me"); - console.log("---"); + logger.info("---"); + logger.info("🔧 API Endpoints registered:"); + logger.info(" GET /api/test"); + logger.info(" GET /api/snippets"); + logger.info(" GET /api/snippets/:id"); + logger.info(" POST /api/snippets"); + logger.info(" PUT /api/snippets/:id"); + logger.info(" DELETE /api/snippets/:id"); + logger.info(" POST /api/snippets/:id/favorite"); + logger.info(" GET /api/languages"); + logger.info(" GET /api/tags"); + logger.info(" POST /api/auth/user"); + logger.info(" GET /api/auth/me"); + logger.info("---"); } ); // Handle process termination gracefully process.on('SIGTERM', () => { - console.log('🛑 SIGTERM received, shutting down gracefully'); + logger.info('🛑 SIGTERM received, shutting down gracefully'); server.close(() => { - console.log('✅ Server closed'); + logger.info('✅ Server closed'); process.exit(0); }); }); process.on('SIGINT', () => { - console.log('🛑 SIGINT received, shutting down gracefully'); + logger.info('🛑 SIGINT received, shutting down gracefully'); server.close(() => { - console.log('✅ Server closed'); + logger.info('✅ Server closed'); process.exit(0); }); }); })().catch((error) => { - console.error('❌ Failed to start server:', error); + logger.error("❌ Failed to start server:", error); process.exit(1); }); diff --git a/server/routes.ts b/server/routes.ts index 5dbd24d..b57f76e 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -6,6 +6,7 @@ import type { DecodedIdToken } from "firebase-admin/auth"; import { pool } from "./db"; import { storage } from "./storage"; import { simpleStorage } from "./simple-storage"; +import logger from "../src/logger.js"; import { insertSnippetSchema, insertCollectionSchema, @@ -19,10 +20,10 @@ import { z } from "zod"; ;(async () => { try { const client = await pool.connect(); - console.log("✅ DATABASE CONNECTION TEST: OK —", (await client.query("SELECT NOW()")).rows[0].now); + logger.info(`✅ DATABASE CONNECTION TEST: OK — ${(await client.query("SELECT NOW()")).rows[0].now}`); client.release(); } catch (e) { - console.error("❌ DATABASE CONNECTION TEST: FAILED", e); + logger.error("❌ DATABASE CONNECTION TEST: FAILED", e); } })(); diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..cef6715 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,19 @@ +import { createLogger, format, transports } from 'winston'; + +const logger = createLogger({ + level: 'info', + format: format.combine( + format.colorize(), + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf(({ timestamp, level, message, ...meta }) => { + const metaString = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `${timestamp} [${level}]: ${message}${metaString}`; + }) + ), + transports: [ + new transports.Console(), + new transports.File({ filename: '/home/zk/logs/codepatchwork.log' }) + ] +}); + +export default logger; From cc947516e29ed72c40d2cfa773a02f4242ddd5e0 Mon Sep 17 00:00:00 2001 From: 0xWulf <0xWulf@proton.me> Date: Mon, 23 Jun 2025 11:51:47 +0800 Subject: [PATCH 09/16] fix: winston logging fix --- ecosystem.config.cjs | 11 +- package-lock.json | 226 +++++++++++++++++++++++++++++++++++++++++ package.json | 1 + server/index.ts | 122 +++++++++------------- server/logger.js | 54 ++++++++++ server/routes.ts | 2 +- server/winston-test.js | 6 ++ src/logger.js | 19 ---- 8 files changed, 346 insertions(+), 95 deletions(-) create mode 100644 server/logger.js create mode 100644 server/winston-test.js delete mode 100644 src/logger.js diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 6417c2b..0b2cf50 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -6,7 +6,16 @@ module.exports = { cwd: "/home/zk/projects/CodePatchwork", env: { NODE_ENV: "production", - PORT: 3001 // Add this line + PORT: 3001, + DATABASE_URL: "postgresql://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork", + PGDATABASE: "codepatchwork", + PGUSER: "codepatchwork_user", + PGPASSWORD: "1S1HwpTVdmilD8tNeGmI", + PGHOST: "localhost", + PGPORT: "5432", + SESSION_SECRET: "d8f49a7c31e5b8a2c6f3e9d1b5a7c2e0f3d6a9b8c5e2f1d4a7b3c6e9f2d5a8c1b4e7f0d3a6b9c2e5f8a1d4b7c0", + VITE_PUBLIC_URL: "https://codepatchwork.com", + GOOGLE_APPLICATION_CREDENTIALS: "/home/zk/projects/CodePatchwork/codepatchwork-c3d85-firebase-adminsdk-fbsvc-cce84a84c0.json" }, instances: 1, autorestart: true, diff --git a/package-lock.json b/package-lock.json index 0416492..2dbcc68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,6 +79,7 @@ "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.2.5", "vaul": "^1.1.2", + "winston": "^3.17.0", "wouter": "^3.3.5", "ws": "^8.18.0", "zod": "^3.24.2", @@ -412,6 +413,24 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -4648,6 +4667,11 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "optional": true }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/ws": { "version": "8.5.13", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", @@ -6828,6 +6852,46 @@ "node": ">=0.1.90" } }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/colorspace/node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/colorspace/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/colorspace/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorspace/node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -8406,6 +8470,11 @@ "node": ">= 4" } }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -9228,6 +9297,11 @@ "node": ">=0.8.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -9421,6 +9495,11 @@ "deprecated": "flatten is deprecated in favor of utility frameworks such as lodash.", "license": "MIT" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -11057,6 +11136,11 @@ "node": ">=0.10.0" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -11529,6 +11613,22 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -12454,6 +12554,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/openid-client": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.5.0.tgz", @@ -15812,6 +15920,14 @@ "ret": "~0.1.10" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -16058,6 +16174,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -16375,6 +16504,14 @@ "node": ">=0.8" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -16787,6 +16924,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -16986,6 +17128,14 @@ "node": ">=0.10.0" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -18590,6 +18740,82 @@ "node": ">= 0.8.0" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/winston/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/wordwrap": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", diff --git a/package.json b/package.json index d63636b..891a57c 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "tailwindcss-animate": "^1.0.7", "tw-animate-css": "^1.2.5", "vaul": "^1.1.2", + "winston": "^3.17.0", "wouter": "^3.3.5", "ws": "^8.18.0", "zod": "^3.24.2", diff --git a/server/index.ts b/server/index.ts index 093549d..f53d559 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,5 +1,4 @@ -// server/index.ts – Express bootstrap for CodePatchwork -/* ------------------------------------------------------------------ */ +// server/index.ts – Express bootstrap for CodePatchwork with Winston logging import dotenv from "dotenv"; dotenv.config(); @@ -8,14 +7,21 @@ import fs from "fs"; import path from "path"; import admin from "firebase-admin"; import express, { Request, Response, NextFunction } from "express"; -import camelCase from "camelcase"; import helmet from "helmet"; +import camelCase from "camelcase"; import { registerRoutes } from "./routes"; -import { setupVite, serveStatic, log } from "./vite"; -import logger from "../src/logger.js"; +import { setupVite, serveStatic } from "./vite"; +import logger from "./logger.js"; + /* ────────────────────────────────────────────────────────────────── */ -/* 0. Verify & load your service-account JSON */ +/* 0. Winston test log – confirms logger is active */ +/* ────────────────────────────────────────────────────────────────── */ +logger.info("✅ Winston logger initialized: /home/zk/logs/codepatchwork.log"); +logger.info("🧪 Logger test: Express server startup log"); + +/* ────────────────────────────────────────────────────────────────── */ +/* 1. Verify & load your service-account JSON */ /* ────────────────────────────────────────────────────────────────── */ const svcPath = path.resolve( process.cwd(), @@ -33,36 +39,32 @@ const serviceAccount = JSON.parse( ); /* ────────────────────────────────────────────────────────────────── */ -/* 1. Initialize Firebase Admin with explicit cert */ +/* 2. Initialize Firebase Admin */ /* ────────────────────────────────────────────────────────────────── */ admin.initializeApp({ credential: admin.credential.cert(serviceAccount), }); /* ────────────────────────────────────────────────────────────────── */ -/* 2. Express + Body parsers */ +/* 3. Express setup */ /* ────────────────────────────────────────────────────────────────── */ const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: false })); /* ────────────────────────────────────────────────────────────────── */ -/* 3. Security headers - Modified to fix Firebase auth */ +/* 4. Helmet security headers */ /* ────────────────────────────────────────────────────────────────── */ -// Disable all helmet protections and apply only what we need app.use( - // Completely disable COOP and COEP policies for auth popups helmet.crossOriginOpenerPolicy({ policy: "unsafe-none" }), helmet.crossOriginEmbedderPolicy({ policy: "unsafe-none" }), - - // Apply minimum security headers to allow Firebase auth helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"], scriptSrc: [ - "'self'", + "'self'", "'unsafe-inline'", - "'unsafe-eval'", // Needed for some Firebase operations + "'unsafe-eval'", "https://www.gstatic.com", "https://apis.google.com", "https://*.firebaseio.com", @@ -89,21 +91,17 @@ app.use( ); /* ────────────────────────────────────────────────────────────────── */ -/* 4. CRITICAL FIX: JSON-only middleware for ALL API routes */ +/* 5. API middleware */ /* ────────────────────────────────────────────────────────────────── */ app.use('/api', (req, res, next) => { logger.info(`[API] ${req.method} ${req.path} - Request received`); - - // Force Content-Type to application/json for all API responses + res.setHeader('Content-Type', 'application/json'); - - // Override Express's default error handling to ensure JSON responses + const originalSend = res.send; const originalStatus = res.status; - - // Ensure res.send always returns JSON for API routes + res.send = function(data: any) { - // If Express tries to send HTML (like error pages), convert to JSON if (typeof data === 'string' && (data.includes(''))) { logger.info(`[API] 🚨 Converting HTML response to JSON for ${req.method} ${req.path}`); this.setHeader('Content-Type', 'application/json'); @@ -117,12 +115,9 @@ app.use('/api', (req, res, next) => { } return originalSend.call(this, data); }; - - // Override res.status to ensure chaining works with JSON + res.status = function(statusCode: number) { const result = originalStatus.call(this, statusCode); - - // If someone calls res.status().send() with HTML, intercept it const newSend = result.send; result.send = function(data: any) { if (typeof data === 'string' && (data.includes(''))) { @@ -138,15 +133,14 @@ app.use('/api', (req, res, next) => { } return newSend.call(this, data); }; - return result; }; - + next(); }); /* ────────────────────────────────────────────────────────────────── */ -/* 5. Normalize /api/snippets payload (snake → camel, ISO dates) */ +/* 6. Normalize response keys */ /* ────────────────────────────────────────────────────────────────── */ app.use("/api/snippets", (_req, res, next) => { type Row = Record; @@ -175,7 +169,7 @@ app.use("/api/snippets", (_req, res, next) => { }); /* ────────────────────────────────────────────────────────────────── */ -/* 6. Simple API request logger */ +/* 7. Performance + payload logging */ /* ────────────────────────────────────────────────────────────────── */ app.use((req, res, next) => { const t0 = Date.now(); @@ -193,7 +187,7 @@ app.use((req, res, next) => { let msg = `${req.method} ${req.path} ${res.statusCode} in ${ms}ms`; if (payload) msg += ` :: ${JSON.stringify(payload)}`; if (msg.length > 80) msg = msg.slice(0, 79) + "…"; - log(msg); + logger.info(msg); } }); @@ -201,19 +195,16 @@ app.use((req, res, next) => { }); /* ────────────────────────────────────────────────────────────────── */ -/* 7. Route registration */ +/* 8. Route registration + boot */ /* ────────────────────────────────────────────────────────────────── */ (async () => { logger.info("🔧 Starting route registration..."); const server = await registerRoutes(app); logger.info("✅ Route registration complete"); - /* ──────────────────────────────────────────────────────────────── */ - /* 8. 404 Handler for unmatched API routes - MUST BE BEFORE GLOBAL */ - /* ──────────────────────────────────────────────────────────────── */ app.use('/api/*', (req, res) => { logger.info(`[404] API route not found: ${req.method} ${req.path}`); - res.status(404).json({ + res.status(404).json({ message: "API endpoint not found", path: req.path, method: req.method, @@ -223,7 +214,7 @@ app.use((req, res, next) => { "GET /api/snippets", "GET /api/snippets/:id", "POST /api/snippets", - "PUT /api/snippets/:id", + "PUT /api/snippets/:id", "DELETE /api/snippets/:id", "POST /api/snippets/:id/favorite", "GET /api/languages", @@ -234,15 +225,9 @@ app.use((req, res, next) => { }); }); - /* ──────────────────────────────────────────────────────────────── */ - /* 9. Enhanced global error handler - MUST BE AFTER 404 HANDLER */ - /* ──────────────────────────────────────────────────────────────── */ app.use((err: any, req: Request, res: Response, next: NextFunction) => { logger.error(`[💥 GLOBAL ERROR] ${req.method} ${req.path}:`, err.stack || err); - - // Ensure we don't send if headers already sent if (!res.headersSent) { - // Always return JSON for API routes if (req.path.startsWith('/api/')) { res.setHeader('Content-Type', 'application/json'); res.status(err.status || 500).json({ @@ -253,49 +238,38 @@ app.use((req, res, next) => { error: process.env.NODE_ENV === 'production' ? 'Something went wrong' : err.stack }); } else { - // For non-API routes, use your original behavior res.status(err.status || 500).json({ message: err.message || "Error" }); } } }); - /* ──────────────────────────────────────────────────────────────── */ - /* 10. Vite in dev or static in prod */ - /* ──────────────────────────────────────────────────────────────── */ if (app.get("env") === "development") { await setupVite(app, server); } else { serveStatic(app); } - /* ──────────────────────────────────────────────────────────────── */ - /* 11. Start HTTP server */ - /* ──────────────────────────────────────────────────────────────── */ const port = Number(process.env.PORT) || 3001; - server.listen( - { host: "0.0.0.0", port, reusePort: true }, - () => { - log(`🚀 Serving on port ${port}`); - log(`📡 API available at http://localhost:${port}/api/`); - log(`🧪 Test API at http://localhost:${port}/api/test`); - logger.info("---"); - logger.info("🔧 API Endpoints registered:"); - logger.info(" GET /api/test"); - logger.info(" GET /api/snippets"); - logger.info(" GET /api/snippets/:id"); - logger.info(" POST /api/snippets"); - logger.info(" PUT /api/snippets/:id"); - logger.info(" DELETE /api/snippets/:id"); - logger.info(" POST /api/snippets/:id/favorite"); - logger.info(" GET /api/languages"); - logger.info(" GET /api/tags"); - logger.info(" POST /api/auth/user"); - logger.info(" GET /api/auth/me"); - logger.info("---"); - } - ); + server.listen({ host: "0.0.0.0", port, reusePort: true }, () => { + logger.info(`🚀 Serving on port ${port}`); + logger.info(`📡 API available at http://localhost:${port}/api/`); + logger.info(`🧪 Test API at http://localhost:${port}/api/test`); + logger.info("---"); + logger.info("🔧 API Endpoints registered:"); + logger.info(" GET /api/test"); + logger.info(" GET /api/snippets"); + logger.info(" GET /api/snippets/:id"); + logger.info(" POST /api/snippets"); + logger.info(" PUT /api/snippets/:id"); + logger.info(" DELETE /api/snippets/:id"); + logger.info(" POST /api/snippets/:id/favorite"); + logger.info(" GET /api/languages"); + logger.info(" GET /api/tags"); + logger.info(" POST /api/auth/user"); + logger.info(" GET /api/auth/me"); + logger.info("---"); + }); - // Handle process termination gracefully process.on('SIGTERM', () => { logger.info('🛑 SIGTERM received, shutting down gracefully'); server.close(() => { diff --git a/server/logger.js b/server/logger.js new file mode 100644 index 0000000..50e380f --- /dev/null +++ b/server/logger.js @@ -0,0 +1,54 @@ +// logger.js — Drop-in Winston logger with safety checks and startup test log +import fs from 'fs'; +import path from 'path'; +import { createLogger, format, transports } from 'winston'; + +// Ensure log directory exists +const logDir = '/home/zk/logs'; +const logFile = path.join(logDir, 'codepatchwork.log'); + +if (!fs.existsSync(logDir)) { + try { + fs.mkdirSync(logDir, { recursive: true }); + console.log(`✅ Created log directory: ${logDir}`); + } catch (err) { + console.error(`❌ Failed to create log directory: ${logDir}`, err); + } +} + +// Define transports +const transportList = [ + new transports.Console(), +]; + +try { + transportList.push( + new transports.File({ filename: logFile }) + ); +} catch (err) { + console.error("❌ Could not create file transport for Winston:", err); +} + +// Create the logger +const logger = createLogger({ + level: 'info', + format: format.combine( + format.colorize(), + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf(({ timestamp, level, message, ...meta }) => { + const metaString = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; + return `${timestamp} [${level}]: ${message}${metaString}`; + }) + ), + transports: transportList +}); + +// Handle internal Winston errors +logger.on('error', (err) => { + console.error("❌ Winston internal logging error:", err); +}); + +// 🔧 Initial test log +logger.info("🧪 Winston logger initialized and ready."); + +export default logger; diff --git a/server/routes.ts b/server/routes.ts index b57f76e..226019e 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -6,7 +6,7 @@ import type { DecodedIdToken } from "firebase-admin/auth"; import { pool } from "./db"; import { storage } from "./storage"; import { simpleStorage } from "./simple-storage"; -import logger from "../src/logger.js"; +import logger from "./logger.js"; import { insertSnippetSchema, insertCollectionSchema, diff --git a/server/winston-test.js b/server/winston-test.js new file mode 100644 index 0000000..b9b6dec --- /dev/null +++ b/server/winston-test.js @@ -0,0 +1,6 @@ +// winston-test.js +import logger from './logger.js'; + +logger.info("✅ Winston basic test: info level"); +logger.warn("⚠️ Winston basic test: warn level"); +logger.error("❌ Winston basic test: error level"); diff --git a/src/logger.js b/src/logger.js deleted file mode 100644 index cef6715..0000000 --- a/src/logger.js +++ /dev/null @@ -1,19 +0,0 @@ -import { createLogger, format, transports } from 'winston'; - -const logger = createLogger({ - level: 'info', - format: format.combine( - format.colorize(), - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(({ timestamp, level, message, ...meta }) => { - const metaString = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; - return `${timestamp} [${level}]: ${message}${metaString}`; - }) - ), - transports: [ - new transports.Console(), - new transports.File({ filename: '/home/zk/logs/codepatchwork.log' }) - ] -}); - -export default logger; From 3471b5f2a737bddc29109e15ac85ac98cd32d3ce Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Mon, 23 Jun 2025 13:14:18 +0900 Subject: [PATCH 10/16] feat: enable winston logging --- package.json | 1 + server/index.ts | 5 +++-- server/{logger.js => logger.ts} | 28 ++++++---------------------- server/routes.ts | 2 +- server/winston-test.js | 2 +- 5 files changed, 12 insertions(+), 26 deletions(-) rename server/{logger.js => logger.ts} (61%) diff --git a/package.json b/package.json index 891a57c..e755b9a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "license": "MIT", + "sideEffects": ["./server/logger.ts"], "scripts": { "dev": "NODE_ENV=development tsx server/index.ts", "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", diff --git a/server/index.ts b/server/index.ts index f53d559..4903a66 100644 --- a/server/index.ts +++ b/server/index.ts @@ -11,13 +11,13 @@ import helmet from "helmet"; import camelCase from "camelcase"; import { registerRoutes } from "./routes"; import { setupVite, serveStatic } from "./vite"; -import logger from "./logger.js"; +import logger from "./logger"; /* ────────────────────────────────────────────────────────────────── */ /* 0. Winston test log – confirms logger is active */ /* ────────────────────────────────────────────────────────────────── */ -logger.info("✅ Winston logger initialized: /home/zk/logs/codepatchwork.log"); +logger.info("✅ Winston logger loaded from ./logger.ts"); logger.info("🧪 Logger test: Express server startup log"); /* ────────────────────────────────────────────────────────────────── */ @@ -250,6 +250,7 @@ app.use((req, res, next) => { } const port = Number(process.env.PORT) || 3001; + logger.info(`🚀 Express server starting on port ${process.env.PORT || 3001}`); server.listen({ host: "0.0.0.0", port, reusePort: true }, () => { logger.info(`🚀 Serving on port ${port}`); logger.info(`📡 API available at http://localhost:${port}/api/`); diff --git a/server/logger.js b/server/logger.ts similarity index 61% rename from server/logger.js rename to server/logger.ts index 50e380f..a282558 100644 --- a/server/logger.js +++ b/server/logger.ts @@ -1,9 +1,7 @@ -// logger.js — Drop-in Winston logger with safety checks and startup test log import fs from 'fs'; import path from 'path'; import { createLogger, format, transports } from 'winston'; -// Ensure log directory exists const logDir = '/home/zk/logs'; const logFile = path.join(logDir, 'codepatchwork.log'); @@ -16,39 +14,25 @@ if (!fs.existsSync(logDir)) { } } -// Define transports -const transportList = [ - new transports.Console(), -]; - -try { - transportList.push( - new transports.File({ filename: logFile }) - ); -} catch (err) { - console.error("❌ Could not create file transport for Winston:", err); -} - -// Create the logger const logger = createLogger({ level: 'info', format: format.combine( - format.colorize(), format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), format.printf(({ timestamp, level, message, ...meta }) => { const metaString = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : ''; return `${timestamp} [${level}]: ${message}${metaString}`; }) ), - transports: transportList + transports: [ + new transports.Console(), + new transports.File({ filename: logFile }) + ] }); -// Handle internal Winston errors logger.on('error', (err) => { - console.error("❌ Winston internal logging error:", err); + console.error('❌ Winston internal logging error:', err); }); -// 🔧 Initial test log -logger.info("🧪 Winston logger initialized and ready."); +logger.info('🧪 Winston logger initialized and ready.'); export default logger; diff --git a/server/routes.ts b/server/routes.ts index 226019e..5ba8a3f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -6,7 +6,7 @@ import type { DecodedIdToken } from "firebase-admin/auth"; import { pool } from "./db"; import { storage } from "./storage"; import { simpleStorage } from "./simple-storage"; -import logger from "./logger.js"; +import logger from "./logger"; import { insertSnippetSchema, insertCollectionSchema, diff --git a/server/winston-test.js b/server/winston-test.js index b9b6dec..bfab35c 100644 --- a/server/winston-test.js +++ b/server/winston-test.js @@ -1,5 +1,5 @@ // winston-test.js -import logger from './logger.js'; +import logger from './logger'; logger.info("✅ Winston basic test: info level"); logger.warn("⚠️ Winston basic test: warn level"); From 92b1ce6c4a3eb5f29d68916fe3cf54c8f963dcdc Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Mon, 23 Jun 2025 14:20:46 +0900 Subject: [PATCH 11/16] fix(winston): ensure file logging survives esbuild --- README.md | 7 +++++++ package.json | 3 ++- server/logger.ts | 16 +++++++++++++++- server/{winston-test.js => winston-test.ts} | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) rename server/{winston-test.js => winston-test.ts} (90%) diff --git a/README.md b/README.md index bbfdbb9..834c8de 100644 --- a/README.md +++ b/README.md @@ -189,9 +189,16 @@ For local development, you'll need Node.js 18+ and PostgreSQL. See detailed setu - `npm run dev` - Start development server - `npm run build` - Build for production +- `npm run test:logger` - Verify Winston file logging in `dist` - `npm run db:push` - Push schema changes to database - `npm run db:studio` - Open Drizzle Studio to manage database +### Quick Logger Test + +After running `npm run build`, execute `npm run test:logger` to verify that +`/home/zk/logs/codepatchwork.log` is created. The test script writes a few +messages using the bundled logger to ensure file logging works in production. + ## 📝 License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/package.json b/package.json index e755b9a..9a10335 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "sideEffects": ["./server/logger.ts"], "scripts": { "dev": "NODE_ENV=development tsx server/index.ts", - "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", + "build": "vite build && esbuild server/index.ts server/winston-test.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", + "test:logger": "node dist/winston-test.js", "start": "NODE_ENV=production node -r dotenv/config dist/index.js", "check": "tsc", "db:push": "drizzle-kit push" diff --git a/server/logger.ts b/server/logger.ts index a282558..6cd21a3 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -14,6 +14,20 @@ if (!fs.existsSync(logDir)) { } } +const fileTransport = new transports.File({ filename: logFile }); + +fileTransport.on('error', err => { + console.error('⚠️ File transport error:', err.message); + try { + fs.appendFileSync( + path.join(logDir, 'fallback.log'), + `[${new Date().toISOString()}] Logger failed: ${err.message}\n` + ); + } catch (appendErr) { + console.error('⚠️ Fallback logging failed:', (appendErr as Error).message); + } +}); + const logger = createLogger({ level: 'info', format: format.combine( @@ -25,7 +39,7 @@ const logger = createLogger({ ), transports: [ new transports.Console(), - new transports.File({ filename: logFile }) + fileTransport ] }); diff --git a/server/winston-test.js b/server/winston-test.ts similarity index 90% rename from server/winston-test.js rename to server/winston-test.ts index bfab35c..c6f69e7 100644 --- a/server/winston-test.js +++ b/server/winston-test.ts @@ -1,4 +1,4 @@ -// winston-test.js +// winston-test.ts import logger from './logger'; logger.info("✅ Winston basic test: info level"); From 5cae5142cca0b366176cc764f107e8e097936df7 Mon Sep 17 00:00:00 2001 From: 0xWulf <0xWulf@proton.me> Date: Mon, 23 Jun 2025 16:53:37 +0800 Subject: [PATCH 12/16] fix Loggging. --- ecosystem.config.cjs | 33 ++++++++++----------------------- package.json | 2 +- server/index.ts | 1 + server/logger.ts | 1 + server/routes.ts | 6 ++++++ 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 0b2cf50..8a6c64e 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -1,25 +1,12 @@ module.exports = { - apps: [{ - name: "codepatchwork", - script: "npm", - args: "start", - cwd: "/home/zk/projects/CodePatchwork", - env: { - NODE_ENV: "production", - PORT: 3001, - DATABASE_URL: "postgresql://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork", - PGDATABASE: "codepatchwork", - PGUSER: "codepatchwork_user", - PGPASSWORD: "1S1HwpTVdmilD8tNeGmI", - PGHOST: "localhost", - PGPORT: "5432", - SESSION_SECRET: "d8f49a7c31e5b8a2c6f3e9d1b5a7c2e0f3d6a9b8c5e2f1d4a7b3c6e9f2d5a8c1b4e7f0d3a6b9c2e5f8a1d4b7c0", - VITE_PUBLIC_URL: "https://codepatchwork.com", - GOOGLE_APPLICATION_CREDENTIALS: "/home/zk/projects/CodePatchwork/codepatchwork-c3d85-firebase-adminsdk-fbsvc-cce84a84c0.json" - }, - instances: 1, - autorestart: true, - watch: false, - max_memory_restart: "1G" - }] + apps: [ + { + name: "codepatchwork", + script: "dist/index.js", + node_args: "-r dotenv/config", + env: { + NODE_ENV: "production" + } + } + ] }; diff --git a/package.json b/package.json index 9a10335..517eb93 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "sideEffects": ["./server/logger.ts"], "scripts": { "dev": "NODE_ENV=development tsx server/index.ts", - "build": "vite build && esbuild server/index.ts server/winston-test.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", + "build": "vite build && esbuild server/index.ts server/winston-test.ts --platform=node --packages=external --bundle --format=esm --outdir=dist --out-extension:.js=.js --tree-shaking=false", "test:logger": "node dist/winston-test.js", "start": "NODE_ENV=production node -r dotenv/config dist/index.js", "check": "tsc", diff --git a/server/index.ts b/server/index.ts index 4903a66..8a02633 100644 --- a/server/index.ts +++ b/server/index.ts @@ -12,6 +12,7 @@ import camelCase from "camelcase"; import { registerRoutes } from "./routes"; import { setupVite, serveStatic } from "./vite"; import logger from "./logger"; +import "./logger.ts"; // force esbuild to preserve it /* ────────────────────────────────────────────────────────────────── */ diff --git a/server/logger.ts b/server/logger.ts index 6cd21a3..71253b8 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -50,3 +50,4 @@ logger.on('error', (err) => { logger.info('🧪 Winston logger initialized and ready.'); export default logger; +export const __loggerSideEffect__ = true; diff --git a/server/routes.ts b/server/routes.ts index 5ba8a3f..5df3507 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -50,6 +50,12 @@ export const authMiddleware: RequestHandler = async (req, res, next) => { /** ─── 3) Register all routes ─────────────────────────────────────────────── */ export async function registerRoutes(app: Express): Promise { + + + app.get('/api/test', (_req, res) => { + res.json({ message: "🧪 API test successful" }); + }); + // ─── 3.0) Health Check Endpoint ────────────────────────────────── app.get("/api/health", async (req: Request, res: Response) => { From 52597b69d53c734aa91f6ced0f29b5fceccc54ee Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Tue, 8 Jul 2025 09:44:03 +0900 Subject: [PATCH 13/16] Remove old database exports and test scripts --- codepatchwork-db-export.zip | Bin 2109 -> 0 bytes db-export/README.md | 50 -- db-export/complete_dump.dump | Bin 1661 -> 0 bytes db-export/data.sql | 23 - db-export/schema.sql | 0 db-test.js | 20 - firebase.bk | 107 ---- output.html | 21 - package.txt | 113 ---- server/db.js | 10 - server/debug_storage.js | 88 --- server/storage.js | 1176 ---------------------------------- server/test-db.js | 86 --- test_db_connection.js | 34 - test_db_lowercase.js | 46 -- test_db_simple.js | 27 - test_dump.sql | 595 ----------------- 17 files changed, 2396 deletions(-) delete mode 100644 codepatchwork-db-export.zip delete mode 100644 db-export/README.md delete mode 100644 db-export/complete_dump.dump delete mode 100644 db-export/data.sql delete mode 100644 db-export/schema.sql delete mode 100644 db-test.js delete mode 100644 firebase.bk delete mode 100644 output.html delete mode 100644 package.txt delete mode 100644 server/db.js delete mode 100644 server/debug_storage.js delete mode 100644 server/storage.js delete mode 100644 server/test-db.js delete mode 100644 test_db_connection.js delete mode 100644 test_db_lowercase.js delete mode 100644 test_db_simple.js delete mode 100644 test_dump.sql diff --git a/codepatchwork-db-export.zip b/codepatchwork-db-export.zip deleted file mode 100644 index debbe75a96b4dec5784aef4bb7dc3c97b2103cae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2109 zcmWIWW@Zs#U|`^2=v=)i>W1{D$6pv37`j*(7&sYZ7=m0KU3^{ja#KP>I2o7=A82NP zaA^fM10%}|W(Ee9*Fa+HWZ(ST20U%=e~Gdbc{bj1y!;1)Ryh?O@ zrkQxgU8}eE?At#{w(RE74VkYV{+@J&Y1^#F%3s&mW&CD-w_MaHi`m_IRE<;pJiWs%h~q zK5Oc@It?#)?K>RwQrSq=_^w2!T=||{A36DlH@->O80%lT!k#_%y6=k_xhwqR-p!s; zCZD=hr>oR(Uqil{=6LqOjd8t6>bapnCBF_o%{dMn()_WN+YK1|FJe9=2&C7 z?PJ%#|30tFi!MzowA>yQnsoo$^@8A^7t$wc#X4pGC^)@q_LRlHrdqMGKYZOZfn(3@ zJvnx{^X%Wp&6msb*}iyP#M-oCtxMAvo@0&uWA^aXgy@<-1$*Pp&Ylrsw{YSV#VN)< zGugtz|&7IX8L(M`F!>v1Cism&mI(LG2C|BtZ4Nik0mg zx@beG;kAQOj>XrDa?*Y;jPBADUNUcXcv6=8xhs#ZTZQILJmkwD@{Rf5Er!E$!&hxx z<+1k4yI1|3OIgI%t#+vmW3S-c_h8w~2vv!-%o(rczg$z(bt$<1!Xb#+&}#N?v&E@f z^yd1OF7S&Fw!UF>=3jR7aW(!Q_fACeI?oDw)aklH`K46EnSB#C<$c?@Q!lZDQ`1>1{Sps!sdF=23}{9sIc8jSoCL7$V_*O#aE2|7AQn=M#|o+O&}zE? zZ&c$@%RY#4AcM9vngg>8wvv#rxtJLb*_lGX${CRjfu><8Q;}VXnZA%st7K(BNo_#$ XK&cJG*Q~&@9a!N5p(`-79)WoPVA*MW diff --git a/db-export/README.md b/db-export/README.md deleted file mode 100644 index 70ac84c..0000000 --- a/db-export/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# CodePatchwork Database Migration - -This folder contains database export files to migrate your CodePatchwork database to a local PostgreSQL instance. - -## Files - -- `schema.sql`: Database schema definition (tables, constraints, etc.) -- `data.sql`: Database data only (no schema) -- `complete_dump.dump`: Complete database export in PostgreSQL custom format - -## Import Instructions - -### Option 1: Using the complete dump (recommended) - -1. Create a new database: - ```bash - createdb codepatchwork - ``` - -2. Import the complete dump: - ```bash - pg_restore -d codepatchwork --no-owner --role=your_username complete_dump.dump - ``` - -### Option 2: Using separate schema and data files - -1. Create a new database: - ```bash - createdb codepatchwork - ``` - -2. Import the schema: - ```bash - psql -d codepatchwork -f schema.sql - ``` - -3. Import the data: - ```bash - psql -d codepatchwork -f data.sql - ``` - -## Database Configuration - -After import, update your local .env file with: - -``` -DATABASE_URL=postgres://your_username:your_password@localhost:5432/codepatchwork -``` - -Replace `your_username` and `your_password` with your local PostgreSQL credentials. diff --git a/db-export/complete_dump.dump b/db-export/complete_dump.dump deleted file mode 100644 index 15b6399af38f82626bd2e8bdad252899da5927ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1661 zcmeH`TT8<*6vs0!AOk@}-&FF}4cu@lIuRdg8@ItOrOmuPl-gxov9_bl@X7D)IoU-f z+lzY>1Nk>6=Op|Px^z8Bz+@$ zFjKiKpVLD3brj1HMWa+HE_bLcAJt*FXAb(wM7Lnf+D6B0v}%^MT8NYfkHbxe+rZ?IAEv@8 zU9YaEjf4by}%l1_#49{lR+x7rOEbR1i!4(|000u I_!~v=1=v>*1poj5 diff --git a/db-export/data.sql b/db-export/data.sql deleted file mode 100644 index 9545b4f..0000000 --- a/db-export/data.sql +++ /dev/null @@ -1,23 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 17.4 (Ubuntu 17.4-1) --- Dumped by pg_dump version 17.4 (Ubuntu 17.4-1) - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET transaction_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - --- --- PostgreSQL database dump complete --- - diff --git a/db-export/schema.sql b/db-export/schema.sql deleted file mode 100644 index e69de29..0000000 diff --git a/db-test.js b/db-test.js deleted file mode 100644 index dc6fa1b..0000000 --- a/db-test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { config } from 'dotenv'; -import pg from 'pg'; -const { Pool } = pg; - -// Load environment variables -config(); - -const pool = new Pool({ - connectionString: process.env.DATABASE_URL -}); - -pool.query('SELECT NOW()', (err, res) => { - if (err) { - console.error('Error connecting to database:', err); - } else { - console.log('Successfully connected to PostgreSQL!'); - console.log('Current time from database:', res.rows[0].now); - } - pool.end(); -}); diff --git a/firebase.bk b/firebase.bk deleted file mode 100644 index f055770..0000000 --- a/firebase.bk +++ /dev/null @@ -1,107 +0,0 @@ -// client/src/lib/firebase.ts -/* ------------------------------------------------------------------ - * Firebase bootstrap for CodePatchwork - * ------------------------------------------------------------------ */ - -import { initializeApp, getApps, getApp } from "firebase/app"; -import { - getAuth, - GoogleAuthProvider, - onAuthStateChanged, - User, -} from "firebase/auth"; - -// 1️⃣ Pull the seven required VITE_ env-vars out of import.meta.env -const { - VITE_FIREBASE_API_KEY, - VITE_FIREBASE_AUTH_DOMAIN, - VITE_FIREBASE_PROJECT_ID, - VITE_FIREBASE_STORAGE_BUCKET, - VITE_FIREBASE_MESSAGING_SENDER_ID, - VITE_FIREBASE_APP_ID, - VITE_FIREBASE_MEASUREMENT_ID, -} = import.meta.env; - -// 2️⃣ Sanity-check: error if any of the “must have” values is missing -if ( - !VITE_FIREBASE_API_KEY || - !VITE_FIREBASE_AUTH_DOMAIN || - !VITE_FIREBASE_PROJECT_ID -) { - throw new Error( - "[Firebase] Missing required VITE_FIREBASE_* env vars. " + - "Make sure your .env is loading them." - ); -} - -// 3️⃣ Build your config object -const firebaseConfig = { - apiKey: VITE_FIREBASE_API_KEY, - authDomain: VITE_FIREBASE_AUTH_DOMAIN, - projectId: VITE_FIREBASE_PROJECT_ID, - storageBucket: VITE_FIREBASE_STORAGE_BUCKET, - messagingSenderId: VITE_FIREBASE_MESSAGING_SENDER_ID, - appId: VITE_FIREBASE_APP_ID, - measurementId: VITE_FIREBASE_MEASUREMENT_ID, -}; - -// 4️⃣ Debug log so you can see exactly what shipped in your bundle -console.log("%c[Firebase cfg]", "color:#4ade80;", firebaseConfig); - -// 5️⃣ Initialise the app (avoiding duplicates on hot-reload) -const app = getApps().length ? getApp() : initializeApp(firebaseConfig); - -// 6️⃣ Set up Auth + Google provider -const auth = getAuth(app); -const googleProvider = new GoogleAuthProvider(); - -// 7️⃣ Listen for sign-in state changes and POST your ID token to the server -if (typeof window !== "undefined") { - onAuthStateChanged(auth, async (user: User | null) => { - if (!user) { - console.log("[Firebase] No user signed in"); - return; - } - - try { - // a) grab a fresh ID token - const idToken = await user.getIdToken(/* forceRefresh */ true); - - // b) send it to your backend - const res = await fetch("/api/auth/user", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ idToken }), - }); - - if (!res.ok) { - console.error( - "🔴 /api/auth/user failed:", - res.status, - await res.text() - ); - } else { - console.log("🟢 /api/auth/user succeeded:", await res.json()); - } - } catch (e) { - console.error("[Firebase] Error sending ID token:", e); - } - }); -} - -// 8️⃣ In non-prod, expose for DevTools debugging -if ( - import.meta.env.MODE !== "production" && - typeof window !== "undefined" -) { - // @ts-ignore - window.__app = app; - // @ts-ignore - window.__auth = auth; - // @ts-ignore - window.__prov = googleProvider; -} - -// 9️⃣ Export for your UI code -export { app, auth, googleProvider }; -export type { User as FirebaseUser }; diff --git a/output.html b/output.html deleted file mode 100644 index 62cd9f2..0000000 --- a/output.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - CodePatchwork - Visual Code Snippet Manager - - - - - - - - - - - -
- - - diff --git a/package.txt b/package.txt deleted file mode 100644 index 3fe36ce..0000000 --- a/package.txt +++ /dev/null @@ -1,113 +0,0 @@ -{ - "name": "rest-express", - "version": "1.0.0", - "type": "module", - "license": "MIT", - "scripts": { - "dev": "NODE_ENV=development tsx server/index.ts", - "build": "vite build && esbuild server/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist", - "start": "NODE_ENV=production node -r dotenv/config dist/index.js" - "check": "tsc", - "db:push": "drizzle-kit push" - }, - "dependencies": { - "@hookform/resolvers": "^3.10.0", - "@jridgewell/trace-mapping": "^0.3.25", - "@neondatabase/serverless": "^0.10.4", - "@radix-ui/react-accordion": "^1.2.4", - "@radix-ui/react-alert-dialog": "^1.1.7", - "@radix-ui/react-aspect-ratio": "^1.1.3", - "@radix-ui/react-avatar": "^1.1.4", - "@radix-ui/react-checkbox": "^1.1.5", - "@radix-ui/react-collapsible": "^1.1.4", - "@radix-ui/react-context-menu": "^2.2.7", - "@radix-ui/react-dialog": "^1.1.7", - "@radix-ui/react-dropdown-menu": "^2.1.7", - "@radix-ui/react-hover-card": "^1.1.7", - "@radix-ui/react-label": "^2.1.3", - "@radix-ui/react-menubar": "^1.1.7", - "@radix-ui/react-navigation-menu": "^1.2.6", - "@radix-ui/react-popover": "^1.1.7", - "@radix-ui/react-progress": "^1.1.3", - "@radix-ui/react-radio-group": "^1.2.4", - "@radix-ui/react-scroll-area": "^1.2.4", - "@radix-ui/react-select": "^2.1.7", - "@radix-ui/react-separator": "^1.1.3", - "@radix-ui/react-slider": "^1.2.4", - "@radix-ui/react-slot": "^1.2.0", - "@radix-ui/react-switch": "^1.1.4", - "@radix-ui/react-tabs": "^1.1.4", - "@radix-ui/react-toast": "^1.2.7", - "@radix-ui/react-toggle": "^1.1.3", - "@radix-ui/react-toggle-group": "^1.1.3", - "@radix-ui/react-tooltip": "^1.2.0", - "@tanstack/react-query": "^5.60.5", - "@types/memoizee": "^0.4.12", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "connect-pg-simple": "^10.0.0", - "date-fns": "^3.6.0", - "dotenv": "^16.5.0", - "drizzle-orm": "^0.39.1", - "drizzle-zod": "^0.7.0", - "embla-carousel-react": "^8.6.0", - "express": "^4.21.2", - "express-session": "^1.18.1", - "firebase": "^11.7.3", - "framer-motion": "^11.13.1", - "input-otp": "^1.4.2", - "lucide-react": "^0.453.0", - "memoizee": "^0.4.17", - "memorystore": "^1.6.7", - "next-themes": "^0.4.6", - "openid-client": "^6.5.0", - "passport": "^0.7.0", - "passport-local": "^1.0.0", - "prism-react-renderer": "^2.4.1", - "prismjs": "^1.30.0", - "react": "^18.3.1", - "react-day-picker": "^8.10.1", - "react-dom": "^18.3.1", - "react-hook-form": "^7.55.0", - "react-icons": "^5.4.0", - "react-prismjs": "^1.0.4", - "react-resizable-panels": "^2.1.7", - "recharts": "^2.15.2", - "tailwind-merge": "^2.6.0", - "tailwindcss-animate": "^1.0.7", - "tw-animate-css": "^1.2.5", - "vaul": "^1.1.2", - "wouter": "^3.3.5", - "ws": "^8.18.0", - "zod": "^3.24.2", - "zod-validation-error": "^3.4.0" - }, - "devDependencies": { - "@replit/vite-plugin-cartographer": "^0.2.0", - "@replit/vite-plugin-runtime-error-modal": "^0.0.3", - "@tailwindcss/typography": "^0.5.15", - "@tailwindcss/vite": "^4.1.3", - "@types/connect-pg-simple": "^7.0.3", - "@types/express": "4.17.21", - "@types/express-session": "^1.18.0", - "@types/node": "20.16.11", - "@types/passport": "^1.0.16", - "@types/passport-local": "^1.0.38", - "@types/react": "^18.3.11", - "@types/react-dom": "^18.3.1", - "@types/ws": "^8.5.13", - "@vitejs/plugin-react": "^4.3.2", - "autoprefixer": "^10.4.20", - "drizzle-kit": "^0.30.6", - "esbuild": "^0.25.0", - "postcss": "^8.4.47", - "tailwindcss": "^3.4.17", - "tsx": "^4.19.1", - "typescript": "5.6.3", - "vite": "^5.4.14" - }, - "optionalDependencies": { - "bufferutil": "^4.0.8" - } -} diff --git a/server/db.js b/server/db.js deleted file mode 100644 index 99b25ed..0000000 --- a/server/db.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Pool, neonConfig } from '@neondatabase/serverless'; -import { drizzle } from 'drizzle-orm/neon-serverless'; -import ws from "ws"; -import * as schema from "@shared/schema"; -neonConfig.webSocketConstructor = ws; -if (!process.env.DATABASE_URL) { - throw new Error("DATABASE_URL must be set. Did you forget to provision a database?"); -} -export var pool = new Pool({ connectionString: process.env.DATABASE_URL }); -export var db = drizzle(pool, { schema: schema }); diff --git a/server/debug_storage.js b/server/debug_storage.js deleted file mode 100644 index 7441be7..0000000 --- a/server/debug_storage.js +++ /dev/null @@ -1,88 +0,0 @@ -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -import { pool } from './db'; -import { DatabaseStorage } from './storage'; -function testDatabaseStorage() { - return __awaiter(this, void 0, void 0, function () { - var storage, snippets, tags, languages, collections, error_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - console.log('Testing DatabaseStorage...'); - storage = new DatabaseStorage(); - _a.label = 1; - case 1: - _a.trys.push([1, 6, 7, 9]); - console.log('Testing getSnippets()...'); - return [4 /*yield*/, storage.getSnippets()]; - case 2: - snippets = _a.sent(); - console.log("Found ".concat(snippets.length, " snippets")); - console.log('Testing getTags()...'); - return [4 /*yield*/, storage.getTags()]; - case 3: - tags = _a.sent(); - console.log("Found ".concat(tags.length, " tags: ").concat(tags.join(', '))); - console.log('Testing getLanguages()...'); - return [4 /*yield*/, storage.getLanguages()]; - case 4: - languages = _a.sent(); - console.log("Found ".concat(languages.length, " languages: ").concat(languages.join(', '))); - console.log('Testing getCollections()...'); - return [4 /*yield*/, storage.getCollections()]; - case 5: - collections = _a.sent(); - console.log("Found ".concat(collections.length, " collections")); - return [3 /*break*/, 9]; - case 6: - error_1 = _a.sent(); - console.error('Error testing DatabaseStorage:', error_1); - return [3 /*break*/, 9]; - case 7: - // Close the database connection pool - return [4 /*yield*/, pool.end()]; - case 8: - // Close the database connection pool - _a.sent(); - return [7 /*endfinally*/]; - case 9: return [2 /*return*/]; - } - }); - }); -} -// Run the test -testDatabaseStorage(); diff --git a/server/storage.js b/server/storage.js deleted file mode 100644 index 4cad0ce..0000000 --- a/server/storage.js +++ /dev/null @@ -1,1176 +0,0 @@ -var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -import { users, snippets, collections, collectionItems, comments } from "@shared/schema"; -import { eq, and, or, isNotNull, ilike, sql, desc, asc } from "drizzle-orm"; -var MemStorage = /** @class */ (function () { - function MemStorage() { - this.users = new Map(); - this.snippets = new Map(); - this.collections = new Map(); - this.collectionItems = new Map(); - this.userIdCounter = 1; - this.snippetIdCounter = 1; - this.collectionIdCounter = 1; - this.collectionItemIdCounter = 1; - // Initialize with some sample data - this.initializeSampleData(); - } - MemStorage.prototype.initializeSampleData = function () { - var _this = this; - // Add sample snippets - var sampleSnippets = [ - { - title: "React useLocalStorage Hook", - description: "Custom React hook to persist state in localStorage with type safety.", - code: "import { useState, useEffect } from 'react';\n\nfunction useLocalStorage(\n key: string, \n initialValue: T\n): [T, (value: T) => void] {\n // Get stored value\n const readValue = (): T => {\n if (typeof window === 'undefined') {\n return initialValue;\n }\n try {\n const item = window.localStorage.getItem(key);\n return item ? JSON.parse(item) : initialValue;\n } catch (error) {\n console.warn('Error reading localStorage key', error);\n return initialValue;\n }\n };\n \n const [storedValue, setStoredValue] = useState(readValue);\n \n // Return a wrapped version of useState's setter\n const setValue = (value: T) => {\n try {\n // Save state\n setStoredValue(value);\n // Save to localStorage\n window.localStorage.setItem(key, JSON.stringify(value));\n } catch (error) {\n console.warn('Error setting localStorage key', error);\n }\n };\n\n useEffect(() => {\n setStoredValue(readValue());\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return [storedValue, setValue];\n}", - language: "tsx", - tags: ["react", "hooks", "typescript"], - userId: null, - isFavorite: false, - viewCount: 12 - }, - { - title: "Python Decorator for Timing", - description: "A simple Python decorator to measure and log function execution time.", - code: "import time\nimport functools\nimport logging\n\ndef timer(func):\n \"\"\"Print the runtime of the decorated function\"\"\"\n @functools.wraps(func)\n def wrapper_timer(*args, **kwargs):\n start_time = time.perf_counter()\n value = func(*args, **kwargs)\n end_time = time.perf_counter()\n run_time = end_time - start_time\n logging.info(f\"Completed {func.__name__!r} in {run_time:.4f} secs\")\n return value\n return wrapper_timer\n\n# Example usage\n@timer\ndef waste_some_time(num_times):\n for _ in range(num_times):\n sum([i**2 for i in range(10000)])\n \n# Call it\nwaste_some_time(100)", - language: "python", - tags: ["python", "decorators", "performance"], - userId: null, - isFavorite: false, - viewCount: 24 - }, - { - title: "CSS Grid Layout Template", - description: "Responsive grid layout with areas for header, sidebar, content and footer.", - code: ".grid-container {\n display: grid;\n grid-template-columns: repeat(12, 1fr);\n grid-template-rows: auto 1fr auto;\n grid-template-areas:\n \"h h h h h h h h h h h h\"\n \"s s c c c c c c c c c c\"\n \"f f f f f f f f f f f f\";\n min-height: 100vh;\n gap: 1rem;\n}\n\n.header { grid-area: h; }\n.sidebar { grid-area: s; }\n.content { grid-area: c; }\n.footer { grid-area: f; }\n\n/* Tablet layout */\n@media (max-width: 992px) {\n .grid-container {\n grid-template-areas:\n \"h h h h h h h h h h h h\"\n \"s s s s c c c c c c c c\"\n \"f f f f f f f f f f f f\";\n }\n}\n\n/* Mobile layout */\n@media (max-width: 768px) {\n .grid-container {\n grid-template-areas:\n \"h h h h h h h h h h h h\"\n \"c c c c c c c c c c c c\"\n \"s s s s s s s s s s s s\"\n \"f f f f f f f f f f f f\";\n }\n}", - language: "css", - tags: ["css", "grid", "responsive"], - userId: null, - isFavorite: true, - viewCount: 41 - }, - { - title: "JavaScript Array Methods Cheatsheet", - description: "Quick reference for common JavaScript array methods with examples.", - code: "/* Array methods cheatsheet */\n\n// ADDING ELEMENTS\narray.push(item); // Add to end\narray.unshift(item); // Add to beginning\narray.splice(index, 0, item); // Add at position\n\n// REMOVING ELEMENTS\narray.pop(); // Remove from end\narray.shift(); // Remove from beginning\narray.splice(index, 1); // Remove at position\n\n// TRANSFORMATION\narray.map(callback); // Create new array with results\narray.filter(callback); // Create array with elements that pass test\narray.reduce(callback, initialValue); // Reduce to single value\narray.sort(compareFunction); // Sort elements\narray.reverse(); // Reverse order\n\n// SEARCHING\narray.find(callback); // Find first matching element\narray.findIndex(callback); // Find index of first match\narray.includes(item); // Check if array contains item\narray.indexOf(item); // Find index of item (-1 if not found)\n\n// ITERATION\narray.forEach(callback); // Execute function on each element\n\n// JOINING & SPLITTING\narray.join(separator); // Join elements into string\nstring.split(separator); // Split string into array", - language: "javascript", - tags: ["javascript", "arrays", "cheatsheet"], - userId: null, - isFavorite: true, - viewCount: 137 - }, - { - title: "Tailwind Dark Mode Toggle", - description: "React component for toggling dark mode with system preference detection.", - code: "import { useState, useEffect } from 'react';\n\nconst DarkModeToggle = () => {\n const [darkMode, setDarkMode] = useState(false);\n\n useEffect(() => {\n // Check for system preference when component mounts\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n setDarkMode(\n localStorage.getItem('darkMode') !== null\n ? localStorage.getItem('darkMode') === 'true'\n : prefersDark\n );\n }, []);\n\n useEffect(() => {\n // Update document class when darkMode state changes\n if (darkMode) {\n document.documentElement.classList.add('dark');\n localStorage.setItem('darkMode', 'true');\n } else {\n document.documentElement.classList.remove('dark');\n localStorage.setItem('darkMode', 'false');\n }\n }, [darkMode]);\n\n return (\n setDarkMode(!darkMode)}\n className=\"p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700\"\n aria-label={darkMode ? 'Switch to light mode' : 'Switch to dark mode'}\n >\n {darkMode ? (\n \n ) : (\n \n )}\n \n );\n};", - language: "jsx", - tags: ["react", "tailwind", "darkmode"], - userId: null, - isFavorite: false, - viewCount: 52 - }, - { - title: "Go Error Handling Pattern", - description: "Best practices for handling errors in Go with custom error types.", - code: "package main\n\nimport (\n \"errors\"\n \"fmt\"\n)\n\n// Define custom error types\ntype NotFoundError struct {\n ID string\n}\n\nfunc (e *NotFoundError) Error() string {\n return fmt.Sprintf(\"entity with ID %s not found\", e.ID)\n}\n\n// Function that returns different error types\nfunc GetUser(id string) (User, error) {\n // Simulate user not found\n if id == \"\" {\n return User{}, &NotFoundError{ID: id}\n }\n \n // Simulate another error\n if id == \"invalid\" {\n return User{}, errors.New(\"invalid user ID format\")\n }\n \n // Success\n return User{ID: id, Name: \"John Doe\"}, nil\n}\n\n// Error handling pattern with type checking\nfunc main() {\n user, err := GetUser(\"\")\n if err != nil {\n // Check specific error type\n if notFoundErr, ok := err.(*NotFoundError); ok {\n fmt.Printf(\"Could not find user: %v\\n\", notFoundErr)\n // Handle not found case\n } else {\n fmt.Printf(\"Error getting user: %v\\n\", err)\n // Handle other errors\n }\n return\n }\n \n // Process the user\n fmt.Printf(\"Found user: %s\\n\", user.Name)\n}", - language: "go", - tags: ["go", "error-handling", "best-practices"], - userId: null, - isFavorite: false, - viewCount: 18 - } - ]; - // Add sample collections - var sampleCollections = [ - { - name: "React Patterns", - description: "Collection of useful React patterns and hooks", - userId: null - }, - { - name: "CSS Layouts", - description: "Responsive CSS layout techniques", - userId: null - }, - { - name: "JavaScript Essentials", - description: "Must-know JavaScript concepts and utilities", - userId: null - } - ]; - // Add all sample snippets - sampleSnippets.forEach(function (snippet) { - _this.createSnippet(__assign(__assign({}, snippet), { viewCount: snippet.viewCount || 0, isFavorite: snippet.isFavorite || false })); - }); - // Add all sample collections - var collectionIds = sampleCollections.map(function (collection) { - return _this.createCollection(collection).then(function (c) { return c.id; }); - }); - // Once all collections are created, add snippets to them - Promise.all(collectionIds).then(function (ids) { - // Add React useLocalStorage and Tailwind Dark Mode Toggle to React Patterns - _this.addSnippetToCollection({ collectionId: ids[0], snippetId: 1 }); - _this.addSnippetToCollection({ collectionId: ids[0], snippetId: 5 }); - // Add CSS Grid Layout to CSS Layouts - _this.addSnippetToCollection({ collectionId: ids[1], snippetId: 3 }); - // Add JavaScript Array Methods to JavaScript Essentials - _this.addSnippetToCollection({ collectionId: ids[2], snippetId: 4 }); - }); - }; - // User operations - MemStorage.prototype.getUser = function (id) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/, this.users.get(id)]; - }); - }); - }; - MemStorage.prototype.getUserByUsername = function (username) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/, Array.from(this.users.values()).find(function (user) { return user.username === username; })]; - }); - }); - }; - MemStorage.prototype.createUser = function (insertUser) { - return __awaiter(this, void 0, void 0, function () { - var id, user; - return __generator(this, function (_a) { - id = this.userIdCounter++; - user = __assign(__assign({}, insertUser), { id: id }); - this.users.set(id, user); - return [2 /*return*/, user]; - }); - }); - }; - // Snippet operations - MemStorage.prototype.getSnippets = function (filters) { - return __awaiter(this, void 0, void 0, function () { - var snippets, languages_1, langLower_1, tags_1, tagLower_1, searchTerm_1; - return __generator(this, function (_a) { - snippets = Array.from(this.snippets.values()); - if (filters) { - // Filter by language - support both single language and multiple languages - if (filters.language) { - if (Array.isArray(filters.language)) { - languages_1 = filters.language.map(function (lang) { return lang.toLowerCase(); }); - snippets = snippets.filter(function (s) { - return s.language && languages_1.includes(s.language.toLowerCase()); - }); - } - else { - langLower_1 = filters.language.toLowerCase(); - snippets = snippets.filter(function (s) { - return s.language && s.language.toLowerCase() === langLower_1; - }); - } - } - // Filter by tag - support both single tag and multiple tags - if (filters.tag) { - if (Array.isArray(filters.tag)) { - tags_1 = filters.tag.map(function (tag) { return tag.toLowerCase(); }); - snippets = snippets.filter(function (s) { var _a; return (_a = s.tags) === null || _a === void 0 ? void 0 : _a.some(function (tag) { return tags_1.includes(tag.toLowerCase()); }); }); - } - else { - tagLower_1 = filters.tag.toLowerCase(); - snippets = snippets.filter(function (s) { var _a; return (_a = s.tags) === null || _a === void 0 ? void 0 : _a.some(function (tag) { return tag.toLowerCase() === tagLower_1; }); }); - } - } - // Filter by favorites - if (filters.favorites) { - snippets = snippets.filter(function (s) { return s.isFavorite; }); - } - // Filter by search term (title, description, code) - if (filters.search) { - searchTerm_1 = filters.search.toLowerCase(); - snippets = snippets.filter(function (s) { - return s.title.toLowerCase().includes(searchTerm_1) || - (s.description && s.description.toLowerCase().includes(searchTerm_1)) || - (s.code && s.code.toLowerCase().includes(searchTerm_1)); - }); - } - } - // Sort by most recently updated - return [2 /*return*/, snippets.sort(function (a, b) { - var dateA = new Date(a.updatedAt).getTime(); - var dateB = new Date(b.updatedAt).getTime(); - return dateB - dateA; - })]; - }); - }); - }; - MemStorage.prototype.getSnippet = function (id) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/, this.snippets.get(id)]; - }); - }); - }; - MemStorage.prototype.createSnippet = function (snippet) { - return __awaiter(this, void 0, void 0, function () { - var id, now, newSnippet; - return __generator(this, function (_a) { - id = this.snippetIdCounter++; - now = new Date(); - newSnippet = __assign(__assign({}, snippet), { id: id, createdAt: now, updatedAt: now, viewCount: snippet.viewCount || 0, isFavorite: snippet.isFavorite || false }); - this.snippets.set(id, newSnippet); - return [2 /*return*/, newSnippet]; - }); - }); - }; - MemStorage.prototype.updateSnippet = function (id, snippet) { - return __awaiter(this, void 0, void 0, function () { - var existingSnippet, updatedSnippet; - return __generator(this, function (_a) { - existingSnippet = this.snippets.get(id); - if (!existingSnippet) { - throw new Error("Snippet with id ".concat(id, " not found")); - } - updatedSnippet = __assign(__assign(__assign({}, existingSnippet), snippet), { updatedAt: new Date() }); - this.snippets.set(id, updatedSnippet); - return [2 /*return*/, updatedSnippet]; - }); - }); - }; - MemStorage.prototype.deleteSnippet = function (id) { - return __awaiter(this, void 0, void 0, function () { - var collectionItemsToDelete, _i, collectionItemsToDelete_1, item; - return __generator(this, function (_a) { - collectionItemsToDelete = Array.from(this.collectionItems.values()) - .filter(function (item) { return item.snippetId === id; }); - for (_i = 0, collectionItemsToDelete_1 = collectionItemsToDelete; _i < collectionItemsToDelete_1.length; _i++) { - item = collectionItemsToDelete_1[_i]; - this.collectionItems.delete(item.id); - } - // Delete the snippet - this.snippets.delete(id); - return [2 /*return*/]; - }); - }); - }; - MemStorage.prototype.incrementSnippetViewCount = function (id) { - return __awaiter(this, void 0, void 0, function () { - var snippet, updatedSnippet; - return __generator(this, function (_a) { - snippet = this.snippets.get(id); - if (snippet) { - updatedSnippet = __assign(__assign({}, snippet), { viewCount: snippet.viewCount + 1 }); - this.snippets.set(id, updatedSnippet); - } - return [2 /*return*/]; - }); - }); - }; - MemStorage.prototype.toggleSnippetFavorite = function (id) { - return __awaiter(this, void 0, void 0, function () { - var snippet, updatedSnippet; - return __generator(this, function (_a) { - snippet = this.snippets.get(id); - if (!snippet) { - throw new Error("Snippet with id ".concat(id, " not found")); - } - updatedSnippet = __assign(__assign({}, snippet), { isFavorite: !snippet.isFavorite }); - this.snippets.set(id, updatedSnippet); - return [2 /*return*/, updatedSnippet]; - }); - }); - }; - // Language and tag operations - MemStorage.prototype.getLanguages = function () { - return __awaiter(this, void 0, void 0, function () { - var languages, _i, _a, snippet; - return __generator(this, function (_b) { - languages = new Set(); - for (_i = 0, _a = this.snippets.values(); _i < _a.length; _i++) { - snippet = _a[_i]; - languages.add(snippet.language); - } - return [2 /*return*/, Array.from(languages).sort()]; - }); - }); - }; - MemStorage.prototype.getTags = function () { - return __awaiter(this, void 0, void 0, function () { - var tags, _i, _a, snippet, _b, _c, tag; - return __generator(this, function (_d) { - tags = new Set(); - for (_i = 0, _a = this.snippets.values(); _i < _a.length; _i++) { - snippet = _a[_i]; - if (snippet.tags) { - for (_b = 0, _c = snippet.tags; _b < _c.length; _b++) { - tag = _c[_b]; - tags.add(tag); - } - } - } - return [2 /*return*/, Array.from(tags).sort()]; - }); - }); - }; - // Collection operations - MemStorage.prototype.getCollections = function () { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/, Array.from(this.collections.values())]; - }); - }); - }; - MemStorage.prototype.getCollection = function (id) { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - return [2 /*return*/, this.collections.get(id)]; - }); - }); - }; - MemStorage.prototype.createCollection = function (collection) { - return __awaiter(this, void 0, void 0, function () { - var id, now, newCollection; - return __generator(this, function (_a) { - id = this.collectionIdCounter++; - now = new Date(); - newCollection = __assign(__assign({}, collection), { id: id, createdAt: now, updatedAt: now }); - this.collections.set(id, newCollection); - return [2 /*return*/, newCollection]; - }); - }); - }; - MemStorage.prototype.updateCollection = function (id, collection) { - return __awaiter(this, void 0, void 0, function () { - var existingCollection, updatedCollection; - return __generator(this, function (_a) { - existingCollection = this.collections.get(id); - if (!existingCollection) { - throw new Error("Collection with id ".concat(id, " not found")); - } - updatedCollection = __assign(__assign(__assign({}, existingCollection), collection), { updatedAt: new Date() }); - this.collections.set(id, updatedCollection); - return [2 /*return*/, updatedCollection]; - }); - }); - }; - MemStorage.prototype.deleteCollection = function (id) { - return __awaiter(this, void 0, void 0, function () { - var collectionItemsToDelete, _i, collectionItemsToDelete_2, item; - return __generator(this, function (_a) { - collectionItemsToDelete = Array.from(this.collectionItems.values()) - .filter(function (item) { return item.collectionId === id; }); - for (_i = 0, collectionItemsToDelete_2 = collectionItemsToDelete; _i < collectionItemsToDelete_2.length; _i++) { - item = collectionItemsToDelete_2[_i]; - this.collectionItems.delete(item.id); - } - // Delete the collection - this.collections.delete(id); - return [2 /*return*/]; - }); - }); - }; - // Collection items operations - MemStorage.prototype.getCollectionSnippets = function (collectionId) { - return __awaiter(this, void 0, void 0, function () { - var items, snippets, _i, items_1, item, snippet; - return __generator(this, function (_a) { - items = Array.from(this.collectionItems.values()) - .filter(function (item) { return item.collectionId === collectionId; }); - snippets = []; - for (_i = 0, items_1 = items; _i < items_1.length; _i++) { - item = items_1[_i]; - snippet = this.snippets.get(item.snippetId); - if (snippet) { - snippets.push(snippet); - } - } - return [2 /*return*/, snippets]; - }); - }); - }; - MemStorage.prototype.addSnippetToCollection = function (collectionItem) { - return __awaiter(this, void 0, void 0, function () { - var snippet, collection, existingItem, id, now, newItem; - return __generator(this, function (_a) { - snippet = this.snippets.get(collectionItem.snippetId); - collection = this.collections.get(collectionItem.collectionId); - if (!snippet) { - throw new Error("Snippet with id ".concat(collectionItem.snippetId, " not found")); - } - if (!collection) { - throw new Error("Collection with id ".concat(collectionItem.collectionId, " not found")); - } - existingItem = Array.from(this.collectionItems.values()).find(function (item) { return item.collectionId === collectionItem.collectionId && - item.snippetId === collectionItem.snippetId; }); - if (existingItem) { - return [2 /*return*/, existingItem]; - } - id = this.collectionItemIdCounter++; - now = new Date(); - newItem = __assign(__assign({}, collectionItem), { id: id, createdAt: now }); - this.collectionItems.set(id, newItem); - return [2 /*return*/, newItem]; - }); - }); - }; - MemStorage.prototype.removeSnippetFromCollection = function (collectionId, snippetId) { - return __awaiter(this, void 0, void 0, function () { - var item; - return __generator(this, function (_a) { - item = Array.from(this.collectionItems.values()).find(function (item) { return item.collectionId === collectionId && item.snippetId === snippetId; }); - if (item) { - this.collectionItems.delete(item.id); - } - return [2 /*return*/]; - }); - }); - }; - return MemStorage; -}()); -export { MemStorage }; -var DatabaseStorage = /** @class */ (function () { - function DatabaseStorage() { - } - // Sharing operations - DatabaseStorage.prototype.getSnippetByShareId = function (shareId) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.select().from(snippets).where(eq(snippets.shareId, shareId)).limit(1)]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.generateShareId = function (snippetId) { - return __awaiter(this, void 0, void 0, function () { - var db, shareId; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - shareId = Math.random().toString(36).substring(2, 10); - // Update the snippet with the new share ID - return [4 /*yield*/, db - .update(snippets) - .set({ shareId: shareId }) - .where(eq(snippets.id, snippetId))]; - case 2: - // Update the snippet with the new share ID - _a.sent(); - return [2 /*return*/, shareId]; - } - }); - }); - }; - DatabaseStorage.prototype.toggleSnippetPublic = function (snippetId) { - return __awaiter(this, void 0, void 0, function () { - var db, currentSnippet, isPublic, shareId, updatedSnippet; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .select() - .from(snippets) - .where(eq(snippets.id, snippetId))]; - case 2: - currentSnippet = (_a.sent())[0]; - if (!currentSnippet) { - throw new Error("Snippet with ID ".concat(snippetId, " not found")); - } - isPublic = !currentSnippet.isPublic; - shareId = currentSnippet.shareId; - if (isPublic && !shareId) { - shareId = Math.random().toString(36).substring(2, 10); - } - return [4 /*yield*/, db - .update(snippets) - .set({ isPublic: isPublic, shareId: shareId }) - .where(eq(snippets.id, snippetId)) - .returning()]; - case 3: - updatedSnippet = (_a.sent())[0]; - return [2 /*return*/, updatedSnippet]; - } - }); - }); - }; - // Comment operations - DatabaseStorage.prototype.getCommentsBySnippetId = function (snippetId) { - return __awaiter(this, void 0, void 0, function () { - var db; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [2 /*return*/, db - .select() - .from(comments) - .where(eq(comments.snippetId, snippetId)) - .orderBy(asc(comments.createdAt))]; - } - }); - }); - }; - DatabaseStorage.prototype.createComment = function (comment) { - return __awaiter(this, void 0, void 0, function () { - var db, newComment; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .insert(comments) - .values(comment) - .returning()]; - case 2: - newComment = (_a.sent())[0]; - return [2 /*return*/, newComment]; - } - }); - }); - }; - DatabaseStorage.prototype.updateComment = function (id, comment) { - return __awaiter(this, void 0, void 0, function () { - var db, updatedComment; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .update(comments) - .set(__assign(__assign({}, comment), { updatedAt: new Date() })) - .where(eq(comments.id, id)) - .returning()]; - case 2: - updatedComment = (_a.sent())[0]; - if (!updatedComment) { - throw new Error("Comment with ID ".concat(id, " not found")); - } - return [2 /*return*/, updatedComment]; - } - }); - }); - }; - DatabaseStorage.prototype.deleteComment = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .delete(comments) - .where(eq(comments.id, id))]; - case 2: - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - DatabaseStorage.prototype.getUser = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.select().from(users).where(eq(users.id, id)).limit(1)]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.getUserByEmail = function (email) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.select().from(users).where(eq(users.email, email)).limit(1)]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.createUser = function (insertUser) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.insert(users).values(insertUser).returning()]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.upsertUser = function (insertUser) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .insert(users) - .values(insertUser) - .onConflictDoUpdate({ - target: users.id, - set: __assign(__assign({}, insertUser), { updatedAt: new Date() }) - }) - .returning()]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.getSnippets = function (filters) { - return __awaiter(this, void 0, void 0, function () { - var db, query, languageConditions, tagArray; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - query = db.select().from(snippets); - if (filters) { - // Handle language filter (single string or array) - if (filters.language) { - if (Array.isArray(filters.language)) { - languageConditions = filters.language.map(function (lang) { - return eq(snippets.language, lang); - }); - if (languageConditions.length > 0) { - query = query.where(or.apply(void 0, languageConditions)); - } - } - else { - // Single language - query = query.where(eq(snippets.language, filters.language)); - } - } - // Handle search filter - if (filters.search) { - query = query.where(or(ilike(snippets.title, "%".concat(filters.search, "%")), ilike(snippets.description || '', "%".concat(filters.search, "%")), ilike(snippets.code, "%".concat(filters.search, "%")))); - } - // Handle tag filter (single string or array) - if (filters.tag) { - if (Array.isArray(filters.tag)) { - tagArray = filters.tag.map(function (t) { return t.toString(); }); - query = query.where(sql(templateObject_1 || (templateObject_1 = __makeTemplateObject(["", " && ARRAY[", "]::text[]"], ["", " && ARRAY[", "]::text[]"])), snippets.tags, tagArray)); - } - else { - // Single tag using contains operator - query = query.where(sql(templateObject_2 || (templateObject_2 = __makeTemplateObject(["", " @> ARRAY[", "]::text[]"], ["", " @> ARRAY[", "]::text[]"])), snippets.tags, filters.tag)); - } - } - // Handle favorites filter - if (filters.favorites) { - query = query.where(eq(snippets.isFavorite, true)); - } - } - // Order by most recently updated - query = query.orderBy(desc(snippets.updatedAt)); - return [4 /*yield*/, query]; - case 2: return [2 /*return*/, _a.sent()]; - } - }); - }); - }; - DatabaseStorage.prototype.getSnippet = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.select().from(snippets).where(eq(snippets.id, id)).limit(1)]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.createSnippet = function (snippet) { - return __awaiter(this, void 0, void 0, function () { - var db, now, snippetToInsert, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - now = new Date(); - snippetToInsert = { - title: snippet.title, - code: snippet.code, - language: snippet.language, - description: snippet.description || null, - tags: snippet.tags || null, - userId: snippet.userId || null, - isFavorite: snippet.isFavorite || false, - // These fields are handled automatically by defaults - // createdAt and updatedAt are set by defaultNow() - // viewCount is set by default(0) - }; - return [4 /*yield*/, db.insert(snippets).values(snippetToInsert).returning()]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.updateSnippet = function (id, snippet) { - return __awaiter(this, void 0, void 0, function () { - var db, existingSnippet, updateData, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getSnippet(id)]; - case 2: - existingSnippet = _a.sent(); - if (!existingSnippet) { - throw new Error("Snippet with id ".concat(id, " not found")); - } - updateData = { - title: snippet.title, - code: snippet.code, - language: snippet.language, - description: snippet.description || null, - tags: snippet.tags || null, - userId: snippet.userId || null, - isFavorite: snippet.isFavorite !== undefined ? snippet.isFavorite : existingSnippet.isFavorite, - updatedAt: new Date() - }; - return [4 /*yield*/, db - .update(snippets) - .set(updateData) - .where(eq(snippets.id, id)) - .returning()]; - case 3: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.deleteSnippet = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, existingSnippet; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getSnippet(id)]; - case 2: - existingSnippet = _a.sent(); - if (!existingSnippet) { - throw new Error("Snippet with id ".concat(id, " not found")); - } - // First delete all collection items that reference this snippet - return [4 /*yield*/, db - .delete(collectionItems) - .where(eq(collectionItems.snippetId, id))]; - case 3: - // First delete all collection items that reference this snippet - _a.sent(); - // Then delete the snippet - return [4 /*yield*/, db - .delete(snippets) - .where(eq(snippets.id, id))]; - case 4: - // Then delete the snippet - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - DatabaseStorage.prototype.incrementSnippetViewCount = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, existingSnippet; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getSnippet(id)]; - case 2: - existingSnippet = _a.sent(); - if (!existingSnippet) { - throw new Error("Snippet with id ".concat(id, " not found")); - } - return [4 /*yield*/, db - .update(snippets) - .set({ - viewCount: sql(templateObject_3 || (templateObject_3 = __makeTemplateObject(["", " + 1"], ["", " + 1"])), snippets.viewCount) - }) - .where(eq(snippets.id, id))]; - case 3: - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - DatabaseStorage.prototype.toggleSnippetFavorite = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, existingSnippet, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getSnippet(id)]; - case 2: - existingSnippet = _a.sent(); - if (!existingSnippet) { - throw new Error("Snippet with id ".concat(id, " not found")); - } - return [4 /*yield*/, db - .update(snippets) - .set({ - isFavorite: !existingSnippet.isFavorite - }) - .where(eq(snippets.id, id)) - .returning()]; - case 3: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.getLanguages = function () { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .selectDistinct({ language: snippets.language }) - .from(snippets)]; - case 2: - result = _a.sent(); - return [2 /*return*/, result.map(function (r) { return r.language; })]; - } - }); - }); - }; - DatabaseStorage.prototype.getTags = function () { - return __awaiter(this, void 0, void 0, function () { - var db, allTags, uniqueTags; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .select({ tags: snippets.tags }) - .from(snippets) - .where(isNotNull(snippets.tags))]; - case 2: - allTags = _a.sent(); - uniqueTags = new Set(); - allTags.forEach(function (row) { - if (row.tags) { - row.tags.forEach(function (tag) { return uniqueTags.add(tag); }); - } - }); - return [2 /*return*/, Array.from(uniqueTags)]; - } - }); - }); - }; - DatabaseStorage.prototype.getCollections = function () { - return __awaiter(this, void 0, void 0, function () { - var db; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.select().from(collections)]; - case 2: return [2 /*return*/, _a.sent()]; - } - }); - }); - }; - DatabaseStorage.prototype.getCollection = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db.select().from(collections).where(eq(collections.id, id)).limit(1)]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.createCollection = function (collection) { - return __awaiter(this, void 0, void 0, function () { - var db, now, collectionWithDefaults, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - now = new Date(); - collectionWithDefaults = __assign(__assign({}, collection), { createdAt: now, updatedAt: now }); - return [4 /*yield*/, db.insert(collections).values(collectionWithDefaults).returning()]; - case 2: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.updateCollection = function (id, collection) { - return __awaiter(this, void 0, void 0, function () { - var db, existingCollection, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getCollection(id)]; - case 2: - existingCollection = _a.sent(); - if (!existingCollection) { - throw new Error("Collection with id ".concat(id, " not found")); - } - return [4 /*yield*/, db - .update(collections) - .set(__assign(__assign({}, collection), { updatedAt: new Date() })) - .where(eq(collections.id, id)) - .returning()]; - case 3: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.deleteCollection = function (id) { - return __awaiter(this, void 0, void 0, function () { - var db, existingCollection; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getCollection(id)]; - case 2: - existingCollection = _a.sent(); - if (!existingCollection) { - throw new Error("Collection with id ".concat(id, " not found")); - } - // First delete all collection items that reference this collection - return [4 /*yield*/, db - .delete(collectionItems) - .where(eq(collectionItems.collectionId, id))]; - case 3: - // First delete all collection items that reference this collection - _a.sent(); - // Then delete the collection - return [4 /*yield*/, db - .delete(collections) - .where(eq(collections.id, id))]; - case 4: - // Then delete the collection - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - DatabaseStorage.prototype.getCollectionSnippets = function (collectionId) { - return __awaiter(this, void 0, void 0, function () { - var db, existingCollection, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getCollection(collectionId)]; - case 2: - existingCollection = _a.sent(); - if (!existingCollection) { - throw new Error("Collection with id ".concat(collectionId, " not found")); - } - return [4 /*yield*/, db - .select() - .from(snippets) - .innerJoin(collectionItems, eq(snippets.id, collectionItems.snippetId)) - .where(eq(collectionItems.collectionId, collectionId))]; - case 3: - result = _a.sent(); - return [2 /*return*/, result.map(function (row) { return (__assign({}, row.snippets)); })]; - } - }); - }); - }; - DatabaseStorage.prototype.addSnippetToCollection = function (collectionItem) { - return __awaiter(this, void 0, void 0, function () { - var db, existingCollection, existingSnippet, existing, now, itemWithDefaults, result; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, this.getCollection(collectionItem.collectionId)]; - case 2: - existingCollection = _a.sent(); - if (!existingCollection) { - throw new Error("Collection with id ".concat(collectionItem.collectionId, " not found")); - } - return [4 /*yield*/, this.getSnippet(collectionItem.snippetId)]; - case 3: - existingSnippet = _a.sent(); - if (!existingSnippet) { - throw new Error("Snippet with id ".concat(collectionItem.snippetId, " not found")); - } - return [4 /*yield*/, db - .select() - .from(collectionItems) - .where(and(eq(collectionItems.collectionId, collectionItem.collectionId), eq(collectionItems.snippetId, collectionItem.snippetId))) - .limit(1)]; - case 4: - existing = _a.sent(); - if (existing.length > 0) { - throw new Error("Snippet is already in the collection"); - } - now = new Date(); - itemWithDefaults = __assign(__assign({}, collectionItem), { createdAt: now }); - return [4 /*yield*/, db - .insert(collectionItems) - .values(itemWithDefaults) - .returning()]; - case 5: - result = _a.sent(); - return [2 /*return*/, result[0]]; - } - }); - }); - }; - DatabaseStorage.prototype.removeSnippetFromCollection = function (collectionId, snippetId) { - return __awaiter(this, void 0, void 0, function () { - var db, existing; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, import('./db')]; - case 1: - db = (_a.sent()).db; - return [4 /*yield*/, db - .select() - .from(collectionItems) - .where(and(eq(collectionItems.collectionId, collectionId), eq(collectionItems.snippetId, snippetId))) - .limit(1)]; - case 2: - existing = _a.sent(); - if (existing.length === 0) { - throw new Error("Snippet is not in the collection"); - } - return [4 /*yield*/, db - .delete(collectionItems) - .where(and(eq(collectionItems.collectionId, collectionId), eq(collectionItems.snippetId, snippetId)))]; - case 3: - _a.sent(); - return [2 /*return*/]; - } - }); - }); - }; - return DatabaseStorage; -}()); -export { DatabaseStorage }; -// Create an instance of DatabaseStorage to use throughout the application -// Using in-memory storage for now until database issues are resolved -// Switch to database storage for persistent data -export var storage = new DatabaseStorage(); -var templateObject_1, templateObject_2, templateObject_3; diff --git a/server/test-db.js b/server/test-db.js deleted file mode 100644 index 29e911e..0000000 --- a/server/test-db.js +++ /dev/null @@ -1,86 +0,0 @@ -import 'dotenv/config'; -import pg from 'pg'; -const { Pool } = pg; - -const connectionString = process.env.DATABASE_URL || 'postgresql://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork'; -console.log("Using connection string:", connectionString.replace(/:[^:]*@/, ':***@')); // Hide password in logs - -const pool = new Pool({ connectionString }); - -async function testDatabase() { - console.log("Testing database connection..."); - let client; - - try { - client = await pool.connect(); - console.log("✅ Successfully connected to database!"); - - // Basic query test - const timeResult = await client.query('SELECT NOW() as time'); - console.log("✅ Database time:", timeResult.rows[0].time); - - // List tables - const tablesResult = await client.query(` - SELECT table_name - FROM information_schema.tables - WHERE table_schema = 'public' - `); - - console.log("\nTables in database:"); - if (tablesResult.rows.length === 0) { - console.log("❌ No tables found in public schema!"); - } else { - tablesResult.rows.forEach(row => { - console.log(`- ${row.table_name}`); - }); - } - - // Try queries on specific tables - try { - const snippetsResult = await client.query('SELECT COUNT(*) FROM snippets'); - console.log(`✅ Found ${snippetsResult.rows[0].count} records in snippets table`); - } catch (e) { - console.log(`❌ Error querying snippets table: ${e.message}`); - } - - try { - const tagsResult = await client.query('SELECT COUNT(*) FROM tags'); - console.log(`✅ Found ${tagsResult.rows[0].count} records in tags table`); - } catch (e) { - console.log(`❌ Error querying tags table: ${e.message}`); - } - - try { - const collectionsResult = await client.query('SELECT COUNT(*) FROM collections'); - console.log(`✅ Found ${collectionsResult.rows[0].count} records in collections table`); - } catch (e) { - console.log(`❌ Error querying collections table: ${e.message}`); - } - - // Check users table - try { - const usersResult = await client.query('SELECT COUNT(*) FROM users'); - console.log(`✅ Found ${usersResult.rows[0].count} records in users table`); - - if (usersResult.rows[0].count > 0) { - // Check column types for the first user - const userColumns = await client.query('SELECT * FROM users LIMIT 1'); - console.log("\nUser table structure:"); - Object.entries(userColumns.rows[0]).forEach(([key, value]) => { - console.log(`- ${key}: ${typeof value} (${value === null ? 'null' : value})`); - }); - } - } catch (e) { - console.log(`❌ Error querying users table: ${e.message}`); - } - - } catch (connectionError) { - console.error("❌ Failed to connect to database:", connectionError); - } finally { - if (client) client.release(); - await pool.end(); - } -} - -// Run the test -testDatabase(); diff --git a/test_db_connection.js b/test_db_connection.js deleted file mode 100644 index 831ca57..0000000 --- a/test_db_connection.js +++ /dev/null @@ -1,34 +0,0 @@ -const { Pool } = require('pg'); - -// Use the same connection string as your application -const pool = new Pool({ - connectionString: 'postgresql://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork' -}); - -async function testConnection() { - try { - // Test basic connection - const client = await pool.connect(); - console.log('Successfully connected to the database'); - - // Test querying the snippets table - const snippetsResult = await client.query('SELECT COUNT(*) FROM snippets'); - console.log('Snippets count:', snippetsResult.rows[0].count); - - // Test querying the tags table - const tagsResult = await client.query('SELECT COUNT(*) FROM tags'); - console.log('Tags count:', tagsResult.rows[0].count); - - // Test querying the collections table - const collectionsResult = await client.query('SELECT COUNT(*) FROM collections'); - console.log('Collections count:', collectionsResult.rows[0].count); - - client.release(); - } catch (error) { - console.error('Database connection error:', error); - } finally { - pool.end(); - } -} - -testConnection(); diff --git a/test_db_lowercase.js b/test_db_lowercase.js deleted file mode 100644 index a2dd4d9..0000000 --- a/test_db_lowercase.js +++ /dev/null @@ -1,46 +0,0 @@ -// Direct database test with lowercase column names -import pg from 'pg'; -const { Pool } = pg; - -const pool = new Pool({ - connectionString: 'postgres://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork' -}); - -async function testLowercaseQueries() { - const client = await pool.connect(); - try { - console.log('Connected to database successfully'); - - // Test snippets table with lowercase column names - console.log('Testing snippets table:'); - const snippetsResult = await client.query('SELECT COUNT(*) FROM snippets'); - console.log(`- Found ${snippetsResult.rows[0].count} snippets`); - - const snippetsSample = await client.query('SELECT id, title, language, userid, createdat, updatedat FROM snippets LIMIT 3'); - console.log('- Sample snippets:', JSON.stringify(snippetsSample.rows, null, 2)); - - // Test collectionItems table - console.log('\nTesting collectionItems table:'); - try { - const collectionItemsResult = await client.query('SELECT COUNT(*) FROM "collectionItems"'); - console.log(`- Found ${collectionItemsResult.rows[0].count} collection items`); - - const columnInfo = await client.query(` - SELECT column_name, data_type - FROM information_schema.columns - WHERE table_name = 'collectionItems' - `); - console.log('- Column information:', columnInfo.rows); - } catch (err) { - console.error('- Error testing collectionItems:', err.message); - } - - } catch (err) { - console.error('Database test error:', err); - } finally { - client.release(); - await pool.end(); - } -} - -testLowercaseQueries(); diff --git a/test_db_simple.js b/test_db_simple.js deleted file mode 100644 index 62f3a59..0000000 --- a/test_db_simple.js +++ /dev/null @@ -1,27 +0,0 @@ -import dotenv from 'dotenv'; -import pg from 'pg'; - -dotenv.config(); -const { Pool } = pg; - -console.log('DATABASE_URL from env:', process.env.DATABASE_URL); - -const pool = new Pool({ - connectionString: process.env.DATABASE_URL -}); - -async function test() { - try { - const client = await pool.connect(); - console.log('Successfully connected to the database!'); - const result = await client.query('SELECT COUNT(*) FROM snippets'); - console.log('Snippets count:', result.rows[0].count); - client.release(); - } catch (error) { - console.error('Error connecting to database:', error); - } finally { - await pool.end(); - } -} - -test(); diff --git a/test_dump.sql b/test_dump.sql deleted file mode 100644 index 3d08e6c..0000000 --- a/test_dump.sql +++ /dev/null @@ -1,595 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 17.5 (Ubuntu 17.5-0ubuntu0.25.04.1) --- Dumped by pg_dump version 17.5 (Ubuntu 17.5-0ubuntu0.25.04.1) - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET transaction_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - -SET default_tablespace = ''; - -SET default_table_access_method = heap; - --- --- Name: collectionItems; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public."collectionItems" ( - id integer NOT NULL, - collectionid integer NOT NULL, - snippetid integer NOT NULL, - createdat timestamp with time zone DEFAULT CURRENT_TIMESTAMP -); - - -ALTER TABLE public."collectionItems" OWNER TO codepatchwork_user; - --- --- Name: collectionItems_id_seq; Type: SEQUENCE; Schema: public; Owner: codepatchwork_user --- - -CREATE SEQUENCE public."collectionItems_id_seq" - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public."collectionItems_id_seq" OWNER TO codepatchwork_user; - --- --- Name: collectionItems_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: codepatchwork_user --- - -ALTER SEQUENCE public."collectionItems_id_seq" OWNED BY public."collectionItems".id; - - --- --- Name: collections; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public.collections ( - id integer NOT NULL, - name character varying NOT NULL, - description text, - userid character varying, - createdat timestamp with time zone DEFAULT CURRENT_TIMESTAMP, - updatedat timestamp with time zone DEFAULT CURRENT_TIMESTAMP -); - - -ALTER TABLE public.collections OWNER TO codepatchwork_user; - --- --- Name: collections_id_seq; Type: SEQUENCE; Schema: public; Owner: codepatchwork_user --- - -CREATE SEQUENCE public.collections_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.collections_id_seq OWNER TO codepatchwork_user; - --- --- Name: collections_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: codepatchwork_user --- - -ALTER SEQUENCE public.collections_id_seq OWNED BY public.collections.id; - - --- --- Name: comments; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public.comments ( - id integer NOT NULL, - snippetid integer NOT NULL, - userid character varying, - content text NOT NULL, - createdat timestamp with time zone DEFAULT CURRENT_TIMESTAMP, - updatedat timestamp with time zone DEFAULT CURRENT_TIMESTAMP -); - - -ALTER TABLE public.comments OWNER TO codepatchwork_user; - --- --- Name: comments_id_seq; Type: SEQUENCE; Schema: public; Owner: codepatchwork_user --- - -CREATE SEQUENCE public.comments_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.comments_id_seq OWNER TO codepatchwork_user; - --- --- Name: comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: codepatchwork_user --- - -ALTER SEQUENCE public.comments_id_seq OWNED BY public.comments.id; - - --- --- Name: sessions; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public.sessions ( - sid character varying NOT NULL, - sess jsonb NOT NULL, - expire timestamp without time zone NOT NULL -); - - -ALTER TABLE public.sessions OWNER TO codepatchwork_user; - --- --- Name: snippets; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public.snippets ( - id integer NOT NULL, - title character varying NOT NULL, - description text, - code text NOT NULL, - language character varying, - tags text[], - userid character varying, - createdat timestamp with time zone DEFAULT CURRENT_TIMESTAMP, - updatedat timestamp with time zone DEFAULT CURRENT_TIMESTAMP, - viewcount integer DEFAULT 0, - isfavorite boolean DEFAULT false, - shareid character varying, - ispublic boolean DEFAULT false -); - - -ALTER TABLE public.snippets OWNER TO codepatchwork_user; - --- --- Name: snippets_id_seq; Type: SEQUENCE; Schema: public; Owner: codepatchwork_user --- - -CREATE SEQUENCE public.snippets_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.snippets_id_seq OWNER TO codepatchwork_user; - --- --- Name: snippets_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: codepatchwork_user --- - -ALTER SEQUENCE public.snippets_id_seq OWNED BY public.snippets.id; - - --- --- Name: tags; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public.tags ( - id integer NOT NULL, - name character varying NOT NULL, - count integer DEFAULT 1 -); - - -ALTER TABLE public.tags OWNER TO codepatchwork_user; - --- --- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: codepatchwork_user --- - -CREATE SEQUENCE public.tags_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - -ALTER SEQUENCE public.tags_id_seq OWNER TO codepatchwork_user; - --- --- Name: tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: codepatchwork_user --- - -ALTER SEQUENCE public.tags_id_seq OWNED BY public.tags.id; - - --- --- Name: users; Type: TABLE; Schema: public; Owner: codepatchwork_user --- - -CREATE TABLE public.users ( - id text NOT NULL, - email character varying, - display_name character varying, - photo_url character varying, - created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP, - updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP -); - - -ALTER TABLE public.users OWNER TO codepatchwork_user; - --- --- Name: collectionItems id; Type: DEFAULT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public."collectionItems" ALTER COLUMN id SET DEFAULT nextval('public."collectionItems_id_seq"'::regclass); - - --- --- Name: collections id; Type: DEFAULT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.collections ALTER COLUMN id SET DEFAULT nextval('public.collections_id_seq'::regclass); - - --- --- Name: comments id; Type: DEFAULT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.comments ALTER COLUMN id SET DEFAULT nextval('public.comments_id_seq'::regclass); - - --- --- Name: snippets id; Type: DEFAULT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.snippets ALTER COLUMN id SET DEFAULT nextval('public.snippets_id_seq'::regclass); - - --- --- Name: tags id; Type: DEFAULT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.tags ALTER COLUMN id SET DEFAULT nextval('public.tags_id_seq'::regclass); - - --- --- Data for Name: collectionItems; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public."collectionItems" (id, collectionid, snippetid, createdat) FROM stdin; -\. - - --- --- Data for Name: collections; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public.collections (id, name, description, userid, createdat, updatedat) FROM stdin; -\. - - --- --- Data for Name: comments; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public.comments (id, snippetid, userid, content, createdat, updatedat) FROM stdin; -\. - - --- --- Data for Name: sessions; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public.sessions (sid, sess, expire) FROM stdin; -\. - - --- --- Data for Name: snippets; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public.snippets (id, title, description, code, language, tags, userid, createdat, updatedat, viewcount, isfavorite, shareid, ispublic) FROM stdin; -1000 Test Direct Insert Testing direct database insertion console.log("Hello from direct insert"); javascript {test,direct-insert} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-21 08:32:36.923858+08 2025-05-21 08:32:36.923858+08 0 f \N f -1001 Auto ID Test Testing auto increment after sequence reset console.log("Testing auto ID assignment"); javascript {test,sequence} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-21 08:34:12.519156+08 2025-05-21 08:34:12.519156+08 0 f \N f -1039 Delete File Deletes a file from the file system. File f = new File("delete_me.txt");\nf.delete(); java {file,delete,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.283129+08 2025-05-23 07:59:25.283129+08 0 f \N f -1040 Platform-Independent Path Separator Constructs a path string using the OS-specific separator. String path = "folder" + File.separator + "file.txt"; java {file,separator,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.284554+08 2025-05-23 07:59:25.284554+08 0 f \N f -1041 Read Binary File Reads a binary file byte-by-byte using FileInputStream. FileInputStream fis = new FileInputStream("binary.dat");\nint b;\nwhile ((b = fis.read()) != -1) {\n System.out.print((char) b);\n}\nfis.close(); java {filestream,binary,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.285917+08 2025-05-23 07:59:25.285917+08 0 f \N f -1005 Java Deep Clone Example Illustrates how to perform a deep clone of an object using the Cloneable interface class Address implements Cloneable {\n String city;\n\n Address(String city) {\n this.city = city;\n }\n\n @Override\n protected Object clone() throws CloneNotSupportedException {\n return new Address(this.city);\n }\n}\n\nclass Person implements Cloneable {\n String name;\n Address address;\n\n Person(String name, Address address) {\n this.name = name;\n this.address = address;\n }\n\n @Override\n protected Object clone() throws CloneNotSupportedException {\n Person cloned = (Person) super.clone();\n cloned.address = (Address) address.clone(); // Deep copy\n return cloned;\n }\n\n public static void main(String[] args) throws CloneNotSupportedException {\n Address addr = new Address("Berlin");\n Person original = new Person("Alice", addr);\n Person clone = (Person) original.clone();\n\n clone.address.city = "Munich";\n\n System.out.println("Original city: " + original.address.city);\n System.out.println("Cloned city: " + clone.address.city);\n }\n} java {java,clone,deepcopy,object,oop} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-21 15:39:34.60282+08 2025-05-21 15:39:34.60282+08 0 f \N f -1006 Java Stream API Filter and Map Example showing how to filter a list and transform its elements using Java Stream API. import java.util.Arrays;\\nimport java.util.List;\\nimport java.util.stream.Collectors;\\n\\npublic class StreamExample {\\n public static void main(String[] args) {\\n List names = Arrays.asList(\\"John\\", \\"Jane\\", \\"Adam\\", \\"Alice\\", \\"Bob\\");\\n \\n // Filter names starting with 'A' and convert to uppercase\\n List filteredNames = names.stream()\\n .filter(name -> name.startsWith(\\"A\\"))\\n .map(String::toUpperCase)\\n .collect(Collectors.toList());\\n \\n // Print the result\\n System.out.println(\\"Names starting with 'A': \\" + filteredNames);\\n }\\n} java {java,streams,functional} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-21 17:13:44.708085+08 2025-05-21 17:13:44.708085+08 0 f \N f -1042 Write Binary File Writes bytes to a binary file using FileOutputStream. FileOutputStream fos = new FileOutputStream("output.bin");\nfos.write(new byte[]{65, 66, 67});\nfos.close(); java {filestream,binary,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.287262+08 2025-05-23 07:59:25.287262+08 0 f \N f -1012 Using HashSet Shows how duplicates are not allowed in a HashSet. HashSet set = new HashSet<>();\nset.add(1);\nset.add(2);\nset.add(1);\nSystem.out.println(set); java {hashset,sets,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.209795+08 2025-05-23 07:57:33.209795+08 0 f \N f -1013 TreeSet Sorted Order Inserts values in a TreeSet to show automatic sorting. TreeSet ts = new TreeSet<>();\nts.add("banana");\nts.add("apple");\nSystem.out.println(ts); java {treeset,sets,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.211336+08 2025-05-23 07:57:33.211336+08 0 f \N f -1043 Buffered Writer Example Writes a line using BufferedWriter to optimize IO. BufferedWriter bw = new BufferedWriter(new FileWriter("buffered.txt"));\nbw.write("Buffered line");\nbw.newLine();\nbw.close(); java {file,bufferedwriter,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.288681+08 2025-05-23 07:59:25.288681+08 0 f \N f -1044 Buffered Reader Example Reads a single line using BufferedReader. BufferedReader br = new BufferedReader(new FileReader("buffered.txt"));\nSystem.out.println(br.readLine());\nbr.close(); java {file,bufferedreader,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.290035+08 2025-05-23 07:59:25.290035+08 0 f \N f -1045 Compress File with GZIP Compresses a string to a .gz file using GZIPOutputStream. GZIPOutputStream gos = new GZIPOutputStream(new FileOutputStream("file.gz"));\ngos.write("Hello".getBytes());\ngos.close(); java {gzip,compress,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.291382+08 2025-05-23 07:59:25.291382+08 0 f \N f -1046 Decompress GZIP File Reads and decompresses a .gz file using GZIPInputStream. GZIPInputStream gis = new GZIPInputStream(new FileInputStream("file.gz"));\nint data;\nwhile ((data = gis.read()) != -1) {\n System.out.print((char) data);\n}\ngis.close(); java {gzip,decompress,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.292737+08 2025-05-23 07:59:25.292737+08 0 f \N f -1047 Try-with-Resources for File Handling Uses try-with-resources for automatic closing of streams. try (BufferedReader br = new BufferedReader(new FileReader("sample.txt"))) {\n System.out.println(br.readLine());\n} catch (IOException e) {\n e.printStackTrace();\n} java {try,resources,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.294059+08 2025-05-23 08:00:18.490287+08 0 t \N f -1074 Replace Characters Replaces underscores with spaces in a string. String cleaned = "Hello_World".replace('_', ' '); java {string,replace,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.425456+08 2025-05-23 08:16:44.425456+08 0 f \N f -1075 Compare Strings (Case-Insensitive) Compares two strings ignoring case. System.out.println("hello".equalsIgnoreCase("HELLO")); java {string,compare,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.426905+08 2025-05-23 08:16:44.426905+08 0 f \N f -1076 Split CSV Line Splits a CSV line into an array of values. String line = ""Apple","Banana","Cherry"";\nString[] fruits = line.replaceAll("\\"", "").split(","); java {string,split,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.428456+08 2025-05-23 08:16:44.428456+08 0 f \N f -1 React useLocalStorage Hook Custom React hook to persist state in localStorage with type safety. import { useState, useEffect } from 'react';\\n\\nfunction useLocalStorage(\\n key: string, \\n initialValue: T\\n): [T, (value: T) => void] {\\n // Get stored value\\n const readValue = (): T => {\\n if (typeof window === 'undefined') {\\n return initialValue;\\n }\\n try {\\n const item = window.localStorage.getItem(key);\\n return item ? JSON.parse(item) : initialValue;\\n } catch (error) {\\n console.warn('Error reading localStorage key', error);\\n return initialValue;\\n }\\n };\\n \\n const [storedValue, setStoredValue] = useState(readValue);\\n \\n // Return a wrapped version of useState's setter\\n const setValue = (value: T) => {\\n try {\\n // Save state\\n setStoredValue(value);\\n // Save to localStorage\\n window.localStorage.setItem(key, JSON.stringify(value));\\n } catch (error) {\\n console.warn('Error setting localStorage key', error);\\n }\\n };\\n\\n useEffect(() => {\\n setStoredValue(readValue());\\n // eslint-disable-next-line react-hooks/exhaustive-deps\\n }, []);\\n\\n return [storedValue, setValue];\\n} tsx {react,hooks,typescript} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-16 16:51:32.268+08 2025-05-16 16:51:32.268+08 12 f \N f -2 Python Decorator for Timing A simple Python decorator to measure and log function execution time. import time\\nimport functools\\nimport logging\\n\\ndef timer(func):\\n \\"\\"\\"Print the runtime of the decorated function\\"\\"\\"\\n @functools.wraps(func)\\n def wrapper_timer(*args, **kwargs):\\n start_time = time.perf_counter()\\n value = func(*args, **kwargs)\\n end_time = time.perf_counter()\\n run_time = end_time - start_time\\n logging.info(f\\"Completed {func.__name__!r} in {run_time:.4f} secs\\")\\n return value\\n return wrapper_timer\\n\\n# Example usage\\n@timer\\ndef waste_some_time(num_times):\\n for _ in range(num_times):\\n sum([i**2 for i in range(10000)])\\n \\n# Call it\\nwaste_some_time(100) python {python,decorators,performance} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-16 16:51:32.268+08 2025-05-16 16:51:32.268+08 24 f \N f -3 CSS Grid Layout Template Responsive grid layout with areas for header, sidebar, content and footer. .grid-container {\\n display: grid;\\n grid-template-columns: repeat(12, 1fr);\\n grid-template-rows: auto 1fr auto;\\n grid-template-areas:\\n \\"h h h h h h h h h h h h\\"\\n \\"s s c c c c c c c c c c\\"\\n \\"f f f f f f f f f f f f\\";\\n min-height: 100vh;\\n gap: 1rem;\\n}\\n\\n.header { grid-area: h; }\\n.sidebar { grid-area: s; }\\n.content { grid-area: c; }\\n.footer { grid-area: f; }\\n\\n/* Tablet layout */\\n@media (max-width: 992px) {\\n .grid-container {\\n grid-template-areas:\\n \\"h h h h h h h h h h h h\\"\\n \\"s s s s c c c c c c c c\\"\\n \\"f f f f f f f f f f f f\\";\\n }\\n}\\n\\n/* Mobile layout */\\n@media (max-width: 768px) {\\n .grid-container {\\n grid-template-areas:\\n \\"h h h h h h h h h h h h\\"\\n \\"c c c c c c c c c c c c\\"\\n \\"s s s s s s s s s s s s\\"\\n \\"f f f f f f f f f f f f\\";\\n }\\n} css {css,grid,responsive} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-16 16:51:32.268+08 2025-05-16 16:51:32.268+08 41 t \N f -1008 Create and Access Array Initializes a simple integer array and accesses the second element. int[] numbers = {10, 20, 30};\nSystem.out.println(numbers[1]); java {arrays,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.201856+08 2025-05-23 07:57:33.201856+08 0 f \N f -1009 Iterate Array with For Loop Uses a standard for loop to iterate through an array. for (int i = 0; i < numbers.length; i++) {\n System.out.println(numbers[i]);\n} java {arrays,loops,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.20457+08 2025-05-23 07:57:33.20457+08 0 f \N f -1010 ArrayList Basic Operations Creates an ArrayList, adds elements, and accesses the first item. ArrayList list = new ArrayList<>();\nlist.add("Apple");\nlist.add("Banana");\nSystem.out.println(list.get(0)); java {arraylist,lists,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.20612+08 2025-05-23 07:57:33.20612+08 0 f \N f -1011 LinkedList as List Demonstrates a LinkedList used as a list. LinkedList cities = new LinkedList<>();\ncities.add("Berlin");\ncities.add("Munich"); java {linkedlist,lists,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.208232+08 2025-05-23 07:57:33.208232+08 0 f \N f -1014 HashMap Key-Value Store Stores key-value pairs and retrieves a value using a key. HashMap scores = new HashMap<>();\nscores.put("Alice", 90);\nscores.put("Bob", 80);\nSystem.out.println(scores.get("Alice")); java {hashmap,maps,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.212849+08 2025-05-23 07:57:33.212849+08 0 f \N f -1015 TreeMap Sorted Keys TreeMap automatically sorts keys. TreeMap map = new TreeMap<>();\nmap.put(2, "B");\nmap.put(1, "A");\nSystem.out.println(map); java {treemap,maps,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.214931+08 2025-05-23 07:57:33.214931+08 0 f \N f -1016 Stack Push and Pop Simulates a stack with push and pop using ArrayDeque. Deque stack = new ArrayDeque<>();\nstack.push(10);\nstack.push(20);\nSystem.out.println(stack.pop()); java {stack,deque,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.216362+08 2025-05-23 07:57:33.216362+08 0 f \N f -1017 Queue Operations Shows basic FIFO queue behavior using LinkedList. Queue queue = new LinkedList<>();\nqueue.add("first");\nqueue.add("second");\nSystem.out.println(queue.remove()); java {queue,linkedlist,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.219071+08 2025-05-23 07:57:33.219071+08 0 f \N f -1018 Iterator Example Demonstrates use of an Iterator on a List. Iterator it = list.iterator();\nwhile(it.hasNext()) {\n System.out.println(it.next());\n} java {iterator,collections,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.220773+08 2025-05-23 07:57:33.220773+08 0 f \N f -1019 Extended For Loop Uses enhanced for loop to iterate over a collection. for(String item : list) {\n System.out.println(item);\n} java {for-each,loop,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.222278+08 2025-05-23 07:57:33.222278+08 0 f \N f -1020 Set Operations Performs set intersection using retainAll. Set set1 = new HashSet<>(List.of("a", "b"));\nSet set2 = new HashSet<>(List.of("b", "c"));\nset1.retainAll(set2);\nSystem.out.println(set1); java {sets,hashset,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.223735+08 2025-05-23 07:57:33.223735+08 0 f \N f -1021 Map Entry Iteration Iterates through a map’s key-value pairs. for (Map.Entry entry : scores.entrySet()) {\n System.out.println(entry.getKey() + ": " + entry.getValue());\n} java {map,iteration,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.225182+08 2025-05-23 07:57:33.225182+08 0 f \N f -1022 Multi-dimensional Array Accesses an element from a 2D array. int[][] matrix = {{1, 2}, {3, 4}};\nSystem.out.println(matrix[1][0]); java {array,2d,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.226667+08 2025-05-23 07:57:33.226667+08 0 f \N f -1023 Check if Collection is Empty Checks whether a collection is empty using isEmpty(). if (list.isEmpty()) {\n System.out.println("List is empty");\n} java {collections,check,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.22807+08 2025-05-23 07:57:33.22807+08 0 f \N f -1024 Map Default Value Uses getOrDefault to avoid null for missing keys. int value = scores.getOrDefault("Carol", 0);\nSystem.out.println(value); java {map,default,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.229462+08 2025-05-23 07:57:33.229462+08 0 f \N f -1025 Deque as Queue Uses offer and poll to simulate queue operations with Deque. Deque deque = new ArrayDeque<>();\ndeque.offer("first");\ndeque.offer("second");\nSystem.out.println(deque.poll()); java {queue,deque,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.231046+08 2025-05-23 07:57:33.231046+08 0 f \N f -1026 Remove from List by Index Removes an element at index 1 from a list. list.remove(1); java {list,remove,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.232745+08 2025-05-23 07:57:33.232745+08 0 f \N f -1027 Check Set Contains Element Checks if a set contains a specific element. System.out.println(set.contains("banana")); java {set,contains,unit4} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:57:33.234356+08 2025-05-23 07:57:33.234356+08 0 f \N f -1028 Check File Exists Checks if the file 'data.txt' exists in the current directory. File file = new File("data.txt");\nSystem.out.println(file.exists()); java {file,check,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.264632+08 2025-05-23 07:59:25.264632+08 0 f \N f -1029 Create New Directory Creates a single new directory named 'mydir'. File dir = new File("mydir");\ndir.mkdir(); java {file,directory,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.266643+08 2025-05-23 07:59:25.266643+08 0 f \N f -1030 Create Nested Directories Creates a nested directory structure. File dirs = new File("a/b/c");\ndirs.mkdirs(); java {file,mkdirs,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.26815+08 2025-05-23 07:59:25.26815+08 0 f \N f -1031 List Files in Directory Lists all files and folders in the current directory. File folder = new File(".");\nFile[] files = folder.listFiles();\nfor (File f : files) {\n System.out.println(f.getName());\n} java {file,list,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.269618+08 2025-05-23 07:59:25.269618+08 0 f \N f -1032 Rename or Move File Renames or moves a file using renameTo(). File oldFile = new File("old.txt");\nFile newFile = new File("new.txt");\noldFile.renameTo(newFile); java {file,rename,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.271116+08 2025-05-23 07:59:25.271116+08 0 f \N f -1033 Write to File Writes a string to a file using FileWriter. FileWriter writer = new FileWriter("output.txt");\nwriter.write("Hello, file!");\nwriter.close(); java {filewriter,write,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.27261+08 2025-05-23 07:59:25.27261+08 0 f \N f -1034 Read Text File Line-by-Line Reads each line of a text file using BufferedReader. BufferedReader reader = new BufferedReader(new FileReader("input.txt"));\nString line;\nwhile ((line = reader.readLine()) != null) {\n System.out.println(line);\n}\nreader.close(); java {filereader,read,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.275448+08 2025-05-23 07:59:25.275448+08 0 f \N f -1035 Copy File Content Copies text from one file to another line-by-line. BufferedReader in = new BufferedReader(new FileReader("input.txt"));\nBufferedWriter out = new BufferedWriter(new FileWriter("copy.txt"));\nString line;\nwhile ((line = in.readLine()) != null) {\n out.write(line);\n out.newLine();\n}\nin.close();\nout.close(); java {file,copy,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.277375+08 2025-05-23 07:59:25.277375+08 0 f \N f -1036 Check if File is Directory Checks whether the file object points to a directory. File f = new File("mydir");\nSystem.out.println(f.isDirectory()); java {file,isDirectory,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.278943+08 2025-05-23 07:59:25.278943+08 0 f \N f -1037 Get File Size Returns the size of a file in bytes. File f = new File("example.txt");\nSystem.out.println(f.length() + " bytes"); java {file,size,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.280375+08 2025-05-23 07:59:25.280375+08 0 f \N f -1038 Check File Permissions Checks if a file is readable and writable. File f = new File("test.txt");\nSystem.out.println(f.canRead());\nSystem.out.println(f.canWrite()); java {file,permissions,unit6} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 07:59:25.281767+08 2025-05-23 07:59:25.281767+08 0 f \N f -1048 Override toString() Method Overrides the toString() method to customize object output. public class Book {\n String title = "Java";\n public String toString() {\n return "Book Title: " + title;\n }\n} java {object,tostring,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.321338+08 2025-05-23 08:16:03.321338+08 0 f \N f -1049 Use toString() Implicitly Demonstrates implicit toString() call when printing an object. Book b = new Book();\nSystem.out.println(b); java {object,tostring,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.324601+08 2025-05-23 08:16:03.324601+08 0 f \N f -1050 Compare with == (Identity) Compares object references with == (should return false). String a = new String("Hello");\nString b = new String("Hello");\nSystem.out.println(a == b); java {object,compare,identity,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.326059+08 2025-05-23 08:16:03.326059+08 0 f \N f -1051 Compare with equals() Compares object content using equals() (should return true). System.out.println(a.equals(b)); java {object,equals,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.327431+08 2025-05-23 08:16:03.327431+08 0 f \N f -1052 Override equals() Method Custom equals method comparing content. public boolean equals(Object obj) {\n if (this == obj) return true;\n if (!(obj instanceof Book)) return false;\n Book b = (Book) obj;\n return this.title.equals(b.title);\n} java {object,equals,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.328735+08 2025-05-23 08:16:03.328735+08 0 f \N f -1053 Override hashCode() Method Generates hashCode aligned with equals(). public int hashCode() {\n return title.hashCode();\n} java {object,hashcode,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.330068+08 2025-05-23 08:16:03.330068+08 0 f \N f -1054 Compare Objects with hashCode() Prints hash codes to check object identity via content. System.out.println(a.hashCode());\nSystem.out.println(b.hashCode()); java {object,hashcode,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.331346+08 2025-05-23 08:16:03.331346+08 0 f \N f -1055 Use compareTo() for Ordering Implements compareTo for sorting Book objects alphabetically. public int compareTo(Book other) {\n return this.title.compareTo(other.title);\n} java {object,compareto,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.332647+08 2025-05-23 08:16:03.332647+08 0 f \N f -1056 CompareTo Return Values Returns negative, zero, or positive based on order. System.out.println(b1.compareTo(b2)); java {object,compareto,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.333934+08 2025-05-23 08:16:03.333934+08 0 f \N f -1057 Implement Comparable Interface Implements Comparable for ordering custom objects. public class Book implements Comparable {\n String title;\n public int compareTo(Book b) {\n return this.title.compareTo(b.title);\n }\n} java {comparable,compareto,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.335189+08 2025-05-23 08:16:03.335189+08 0 f \N f -1058 Call clone() with Cloneable Implements clone() with Cloneable interface. public class Book implements Cloneable {\n public Object clone() throws CloneNotSupportedException {\n return super.clone();\n }\n} java {object,clone,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.336523+08 2025-05-23 08:16:03.336523+08 0 f \N f -1059 Create Shallow Copy Clones an object using the overridden clone() method. Book b2 = (Book) b1.clone(); java {object,clone,shallow,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.337782+08 2025-05-23 08:16:03.337782+08 0 f \N f -1060 Check equals Reflexive Verifies reflexivity of equals() method. System.out.println(b1.equals(b1)); // should be true java {equals,reflexive,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.339087+08 2025-05-23 08:16:03.339087+08 0 f \N f -1061 Check equals Symmetric Tests symmetry of equals() implementation. System.out.println(b1.equals(b2));\nSystem.out.println(b2.equals(b1)); java {equals,symmetric,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.340391+08 2025-05-23 08:16:03.340391+08 0 f \N f -1062 Check equals Transitive Tests transitivity of equals() for three objects. System.out.println(b1.equals(b2) && b2.equals(b3) && b1.equals(b3)); java {equals,transitive,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.341683+08 2025-05-23 08:16:03.341683+08 0 f \N f -1063 Check equals with null Ensures equals returns false when compared to null. System.out.println(b1.equals(null)); // should be false java {equals,"null",unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.343124+08 2025-05-23 08:16:03.343124+08 0 f \N f -1064 Call toString() Explicitly Calls toString() explicitly for formatted string output. System.out.println(b.toString()); java {object,tostring,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.344644+08 2025-05-23 08:16:03.344644+08 0 f \N f -1065 Custom toString() Format Formats output with custom brackets. public String toString() {\n return "[Title: " + title + "]";\n} java {tostring,custom,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.346062+08 2025-05-23 08:16:03.346062+08 0 f \N f -1066 Deep Copy Hint Comment hint for implementing deep copy instead of shallow. // Deep copy requires cloning referenced objects manually. java {clone,deep,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.347527+08 2025-05-23 08:16:03.347527+08 0 f \N f -1067 Compare Wrapper Objects Shows behavior of == with wrapper class caching. Integer a = 100;\nInteger b = 100;\nSystem.out.println(a == b); // true due to caching java {compare,wrapper,unit2} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:03.349164+08 2025-05-23 08:16:03.349164+08 0 f \N f -1068 String Literal Declaration Declares and initializes a string using literal syntax. String greeting = "Hello, Java!"; java {string,literal,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.416772+08 2025-05-23 08:16:44.416772+08 0 f \N f -1069 Concatenate Strings Joins multiple strings using the + operator. String fullName = "John" + " " + "Doe"; java {string,concatenation,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.418712+08 2025-05-23 08:16:44.418712+08 0 f \N f -1070 Convert int to String Converts an integer to a string using valueOf(). String numberStr = String.valueOf(123); java {string,conversion,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.420083+08 2025-05-23 08:16:44.420083+08 0 f \N f -1071 Convert String to int Parses an integer value from a string. int number = Integer.parseInt("123"); java {string,parsing,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.421445+08 2025-05-23 08:16:44.421445+08 0 f \N f -1072 Check String Contains Checks if a substring exists within a string. System.out.println("Java Programming".contains("gram")); java {string,contains,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.422808+08 2025-05-23 08:16:44.422808+08 0 f \N f -1073 Get Substring Extracts a substring from the string. String name = "Johnathan";\nSystem.out.println(name.substring(0, 4)); java {string,substring,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.42409+08 2025-05-23 08:16:44.42409+08 0 f \N f -1077 StringBuffer Append Appends strings using StringBuffer for performance. StringBuffer sb = new StringBuffer();\nsb.append("Hello");\nsb.append(" World");\nSystem.out.println(sb.toString()); java {stringbuffer,append,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.42982+08 2025-05-23 08:16:44.42982+08 0 f \N f -1078 Trim Whitespace Removes leading and trailing whitespace from a string. String input = " padded ";\nSystem.out.println(input.trim()); java {string,trim,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.431378+08 2025-05-23 08:16:44.431378+08 0 f \N f -1079 Create Current Date Creates a Date object representing the current moment. Date now = new Date();\nSystem.out.println(now); java {date,current,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.432939+08 2025-05-23 08:16:44.432939+08 0 f \N f -1080 Format Date Formats the current date in German style. SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");\nString formatted = sdf.format(new Date());\nSystem.out.println(formatted); java {date,format,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.434376+08 2025-05-23 08:16:44.434376+08 0 f \N f -1081 Parse Date from String Parses a date string into a Date object. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");\nDate d = sdf.parse("2025-05-23"); java {date,parse,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.435832+08 2025-05-23 08:16:44.435832+08 0 f \N f -1082 Check Date Order Compares two dates to check chronological order. Date earlier = sdf.parse("2020-01-01");\nDate later = new Date();\nSystem.out.println(earlier.before(later)); java {date,compare,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.437176+08 2025-05-23 08:16:44.437176+08 0 f \N f -1083 Use GregorianCalendar Creates a GregorianCalendar for a specific date. Calendar cal = new GregorianCalendar(2025, Calendar.MAY, 23);\nSystem.out.println(cal.getTime()); java {calendar,gregorian,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.438526+08 2025-05-23 08:16:44.438526+08 0 f \N f -1084 Roll Calendar Date Rolls the calendar forward by one month. cal.roll(Calendar.MONTH, 1);\nSystem.out.println(cal.getTime()); java {calendar,roll,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.440895+08 2025-05-23 08:16:44.440895+08 0 f \N f -1085 Get Calendar Field Extracts the year from a Calendar instance. int year = cal.get(Calendar.YEAR);\nSystem.out.println("Year: " + year); java {calendar,get,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.442459+08 2025-05-23 08:16:44.442459+08 0 f \N f -1086 Set Calendar Field Sets a specific day in a Calendar object. cal.set(Calendar.DAY_OF_MONTH, 15);\nSystem.out.println(cal.getTime()); java {calendar,set,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.443856+08 2025-05-23 08:16:44.443856+08 0 f \N f -1087 Format with DateFormat.SHORT Formats a date using a predefined short format. DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);\nSystem.out.println(df.format(new Date())); java {dateformat,short,unit5} rSH4fsZZboZhREcZjnwjOTkxit02 2025-05-23 08:16:44.445158+08 2025-05-23 08:16:44.445158+08 0 f \N f -\. - - --- --- Data for Name: tags; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public.tags (id, name, count) FROM stdin; -1 typescript 1 -2 css 1 -3 python 1 -4 hooks 1 -5 responsive 1 -6 grid 1 -7 decorators 1 -8 performance 1 -9 react 1 -\. - - --- --- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: codepatchwork_user --- - -COPY public.users (id, email, display_name, photo_url, created_at, updated_at) FROM stdin; -T2VGNpXA0CTxChHJbFBp7GfhYUl2 zkai8790@gmail.com 張愷恩 https://lh3.googleusercontent.com/a/ACg8ocJ5myoc01U_tdg7cSutam1CBmfZPGL3jVz_rMQI20RZcSOFZxr9=s96-c 2025-05-20 07:50:53.611165+08 2025-05-20 19:29:38.979+08 -U0vTskMoAfextb67hjU3yVdpx273 randomork3@gmail.com Random Ork https://lh3.googleusercontent.com/a/ACg8ocI2sqIbZiMTq80e2WH1HLFzvHzsq4VQ064pYgE5pMDNouHlf3W3rw=s96-c 2025-05-20 21:18:35.391743+08 2025-05-20 22:20:26.667+08 -rSH4fsZZboZhREcZjnwjOTkxit02 hex316aa@gmail.com Erling Wulf Weinreich https://lh3.googleusercontent.com/a/ACg8ocItsHJMo2sJnkzRkAgfPJvr5lvnfAv-rrC7Yx8B187u2eLsElCO=s96-c 2025-05-20 07:26:10.504186+08 2025-05-23 08:30:50.995+08 -IfA9Mk31L2anZKqVFesYUmWwD9s2 hexawulf@gmail.com Erling Wulf Weinreich https://lh3.googleusercontent.com/a/ACg8ocICU2s0uzUhp110YqFEYWxzzo8BktSem8KsrMp6OMmMzrt8ciy_=s96-c 2025-05-20 08:30:16.370983+08 2025-05-22 16:44:04.342+08 -\. - - --- --- Name: collectionItems_id_seq; Type: SEQUENCE SET; Schema: public; Owner: codepatchwork_user --- - -SELECT pg_catalog.setval('public."collectionItems_id_seq"', 1, false); - - --- --- Name: collections_id_seq; Type: SEQUENCE SET; Schema: public; Owner: codepatchwork_user --- - -SELECT pg_catalog.setval('public.collections_id_seq', 1, false); - - --- --- Name: comments_id_seq; Type: SEQUENCE SET; Schema: public; Owner: codepatchwork_user --- - -SELECT pg_catalog.setval('public.comments_id_seq', 1, false); - - --- --- Name: snippets_id_seq; Type: SEQUENCE SET; Schema: public; Owner: codepatchwork_user --- - -SELECT pg_catalog.setval('public.snippets_id_seq', 1087, true); - - --- --- Name: tags_id_seq; Type: SEQUENCE SET; Schema: public; Owner: codepatchwork_user --- - -SELECT pg_catalog.setval('public.tags_id_seq', 9, true); - - --- --- Name: collectionItems collectionItems_collectionid_snippetid_key; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public."collectionItems" - ADD CONSTRAINT "collectionItems_collectionid_snippetid_key" UNIQUE (collectionid, snippetid); - - --- --- Name: collectionItems collectionItems_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public."collectionItems" - ADD CONSTRAINT "collectionItems_pkey" PRIMARY KEY (id); - - --- --- Name: collections collections_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.collections - ADD CONSTRAINT collections_pkey PRIMARY KEY (id); - - --- --- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.comments - ADD CONSTRAINT comments_pkey PRIMARY KEY (id); - - --- --- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.sessions - ADD CONSTRAINT sessions_pkey PRIMARY KEY (sid); - - --- --- Name: snippets snippets_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.snippets - ADD CONSTRAINT snippets_pkey PRIMARY KEY (id); - - --- --- Name: tags tags_name_key; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.tags - ADD CONSTRAINT tags_name_key UNIQUE (name); - - --- --- Name: tags tags_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.tags - ADD CONSTRAINT tags_pkey PRIMARY KEY (id); - - --- --- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: codepatchwork_user --- - -ALTER TABLE ONLY public.users - ADD CONSTRAINT users_pkey PRIMARY KEY (id); - - --- --- Name: idx_collectionitems_collectionid; Type: INDEX; Schema: public; Owner: codepatchwork_user --- - -CREATE INDEX idx_collectionitems_collectionid ON public."collectionItems" USING btree (collectionid); - - --- --- Name: idx_collectionitems_snippetid; Type: INDEX; Schema: public; Owner: codepatchwork_user --- - -CREATE INDEX idx_collectionitems_snippetid ON public."collectionItems" USING btree (snippetid); - - --- --- Name: idx_comments_snippetid; Type: INDEX; Schema: public; Owner: codepatchwork_user --- - -CREATE INDEX idx_comments_snippetid ON public.comments USING btree (snippetid); - - --- --- Name: idx_session_expire; Type: INDEX; Schema: public; Owner: codepatchwork_user --- - -CREATE INDEX idx_session_expire ON public.sessions USING btree (expire); - - --- --- Name: SCHEMA public; Type: ACL; Schema: -; Owner: pg_database_owner --- - -GRANT ALL ON SCHEMA public TO codepatchwork_user; - - --- --- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: public; Owner: codepatchwork_user --- - -ALTER DEFAULT PRIVILEGES FOR ROLE codepatchwork_user IN SCHEMA public GRANT ALL ON SEQUENCES TO codepatchwork_user; - - --- --- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: public; Owner: codepatchwork_user --- - -ALTER DEFAULT PRIVILEGES FOR ROLE codepatchwork_user IN SCHEMA public GRANT ALL ON TABLES TO codepatchwork_user; - - --- --- PostgreSQL database dump complete --- - From 93849353e5c31dadfaa772fdff0317e79d642915 Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Tue, 8 Jul 2025 09:44:57 +0900 Subject: [PATCH 14/16] Remove example DB connection scripts and document env variable --- .env.example | 1 + server/test-db.js | 86 ------------------------------------------- test_db_connection.js | 34 ----------------- test_db_lowercase.js | 46 ----------------------- 4 files changed, 1 insertion(+), 166 deletions(-) delete mode 100644 server/test-db.js delete mode 100644 test_db_connection.js delete mode 100644 test_db_lowercase.js diff --git a/.env.example b/.env.example index 6da83b5..921c359 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ # Port Configuration PORT=3001 # PostgreSQL Configuration +# Replace with the connection string for your database DATABASE_URL=postgresql://username:password@localhost:5432/codepatchwork PGDATABASE=codepatchwork PGUSER=username diff --git a/server/test-db.js b/server/test-db.js deleted file mode 100644 index 29e911e..0000000 --- a/server/test-db.js +++ /dev/null @@ -1,86 +0,0 @@ -import 'dotenv/config'; -import pg from 'pg'; -const { Pool } = pg; - -const connectionString = process.env.DATABASE_URL || 'postgresql://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork'; -console.log("Using connection string:", connectionString.replace(/:[^:]*@/, ':***@')); // Hide password in logs - -const pool = new Pool({ connectionString }); - -async function testDatabase() { - console.log("Testing database connection..."); - let client; - - try { - client = await pool.connect(); - console.log("✅ Successfully connected to database!"); - - // Basic query test - const timeResult = await client.query('SELECT NOW() as time'); - console.log("✅ Database time:", timeResult.rows[0].time); - - // List tables - const tablesResult = await client.query(` - SELECT table_name - FROM information_schema.tables - WHERE table_schema = 'public' - `); - - console.log("\nTables in database:"); - if (tablesResult.rows.length === 0) { - console.log("❌ No tables found in public schema!"); - } else { - tablesResult.rows.forEach(row => { - console.log(`- ${row.table_name}`); - }); - } - - // Try queries on specific tables - try { - const snippetsResult = await client.query('SELECT COUNT(*) FROM snippets'); - console.log(`✅ Found ${snippetsResult.rows[0].count} records in snippets table`); - } catch (e) { - console.log(`❌ Error querying snippets table: ${e.message}`); - } - - try { - const tagsResult = await client.query('SELECT COUNT(*) FROM tags'); - console.log(`✅ Found ${tagsResult.rows[0].count} records in tags table`); - } catch (e) { - console.log(`❌ Error querying tags table: ${e.message}`); - } - - try { - const collectionsResult = await client.query('SELECT COUNT(*) FROM collections'); - console.log(`✅ Found ${collectionsResult.rows[0].count} records in collections table`); - } catch (e) { - console.log(`❌ Error querying collections table: ${e.message}`); - } - - // Check users table - try { - const usersResult = await client.query('SELECT COUNT(*) FROM users'); - console.log(`✅ Found ${usersResult.rows[0].count} records in users table`); - - if (usersResult.rows[0].count > 0) { - // Check column types for the first user - const userColumns = await client.query('SELECT * FROM users LIMIT 1'); - console.log("\nUser table structure:"); - Object.entries(userColumns.rows[0]).forEach(([key, value]) => { - console.log(`- ${key}: ${typeof value} (${value === null ? 'null' : value})`); - }); - } - } catch (e) { - console.log(`❌ Error querying users table: ${e.message}`); - } - - } catch (connectionError) { - console.error("❌ Failed to connect to database:", connectionError); - } finally { - if (client) client.release(); - await pool.end(); - } -} - -// Run the test -testDatabase(); diff --git a/test_db_connection.js b/test_db_connection.js deleted file mode 100644 index 831ca57..0000000 --- a/test_db_connection.js +++ /dev/null @@ -1,34 +0,0 @@ -const { Pool } = require('pg'); - -// Use the same connection string as your application -const pool = new Pool({ - connectionString: 'postgresql://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork' -}); - -async function testConnection() { - try { - // Test basic connection - const client = await pool.connect(); - console.log('Successfully connected to the database'); - - // Test querying the snippets table - const snippetsResult = await client.query('SELECT COUNT(*) FROM snippets'); - console.log('Snippets count:', snippetsResult.rows[0].count); - - // Test querying the tags table - const tagsResult = await client.query('SELECT COUNT(*) FROM tags'); - console.log('Tags count:', tagsResult.rows[0].count); - - // Test querying the collections table - const collectionsResult = await client.query('SELECT COUNT(*) FROM collections'); - console.log('Collections count:', collectionsResult.rows[0].count); - - client.release(); - } catch (error) { - console.error('Database connection error:', error); - } finally { - pool.end(); - } -} - -testConnection(); diff --git a/test_db_lowercase.js b/test_db_lowercase.js deleted file mode 100644 index a2dd4d9..0000000 --- a/test_db_lowercase.js +++ /dev/null @@ -1,46 +0,0 @@ -// Direct database test with lowercase column names -import pg from 'pg'; -const { Pool } = pg; - -const pool = new Pool({ - connectionString: 'postgres://codepatchwork_user:1S1HwpTVdmilD8tNeGmI@localhost:5432/codepatchwork' -}); - -async function testLowercaseQueries() { - const client = await pool.connect(); - try { - console.log('Connected to database successfully'); - - // Test snippets table with lowercase column names - console.log('Testing snippets table:'); - const snippetsResult = await client.query('SELECT COUNT(*) FROM snippets'); - console.log(`- Found ${snippetsResult.rows[0].count} snippets`); - - const snippetsSample = await client.query('SELECT id, title, language, userid, createdat, updatedat FROM snippets LIMIT 3'); - console.log('- Sample snippets:', JSON.stringify(snippetsSample.rows, null, 2)); - - // Test collectionItems table - console.log('\nTesting collectionItems table:'); - try { - const collectionItemsResult = await client.query('SELECT COUNT(*) FROM "collectionItems"'); - console.log(`- Found ${collectionItemsResult.rows[0].count} collection items`); - - const columnInfo = await client.query(` - SELECT column_name, data_type - FROM information_schema.columns - WHERE table_name = 'collectionItems' - `); - console.log('- Column information:', columnInfo.rows); - } catch (err) { - console.error('- Error testing collectionItems:', err.message); - } - - } catch (err) { - console.error('Database test error:', err); - } finally { - client.release(); - await pool.end(); - } -} - -testLowercaseQueries(); From 755149d3b0f9093241f25cb38587e5cbae439655 Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Tue, 8 Jul 2025 09:45:45 +0900 Subject: [PATCH 15/16] chore: silence logs in production --- client/src/main.tsx | 5 +++++ server/index.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/client/src/main.tsx b/client/src/main.tsx index e92b8e7..bf2c5da 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -7,6 +7,11 @@ import { AuthProvider } from "./contexts/AuthContext"; import { QueryClientProvider } from "@tanstack/react-query"; import { queryClient } from "@/lib/queryClient"; +// Disable console.log in production to avoid verbose output +if (process.env.NODE_ENV === "production") { + console.log = () => {}; +} + const root = createRoot(document.getElementById("root")!); root.render( diff --git a/server/index.ts b/server/index.ts index 8a02633..3ef53b4 100644 --- a/server/index.ts +++ b/server/index.ts @@ -14,6 +14,11 @@ import { setupVite, serveStatic } from "./vite"; import logger from "./logger"; import "./logger.ts"; // force esbuild to preserve it +// Disable console.log in production to avoid verbose output +if (process.env.NODE_ENV === "production") { + console.log = () => {}; +} + /* ────────────────────────────────────────────────────────────────── */ /* 0. Winston test log – confirms logger is active */ From 8cc45655061dbe0920584cccc33e7bd94447d878 Mon Sep 17 00:00:00 2001 From: 0xWulf Date: Tue, 8 Jul 2025 10:28:05 +0900 Subject: [PATCH 16/16] Add pg dependency --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 2dbcc68..86b0746 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "openid-client": "^6.5.0", "passport": "^0.7.0", "passport-local": "^1.0.0", + "pg": "^8.12.0", "prism-react-renderer": "^2.4.1", "prismjs": "^1.30.0", "react": "^18.3.1", diff --git a/package.json b/package.json index 517eb93..1118970 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "openid-client": "^6.5.0", "passport": "^0.7.0", "passport-local": "^1.0.0", + "pg": "^8.12.0", "prism-react-renderer": "^2.4.1", "prismjs": "^1.30.0", "react": "^18.3.1",