2d;oymczc~uy47-I6n&R2)I@&6g<-6z&>z-H!FsH#jvkfp`&7O=fOp {|u zEF-3OQaj>6XPb??BwcVOQd1 B@!!R-R)wN7E1c-lJHw+{A!+srNNVc&Y#w_d@25{`m90 C1#8(`lC*avPWZ}u+idl&Y-tFYa?1l)OW(Hj-Ehv#pEeH&pPxP3eleH-x}?0Zka zKOb&_y8$kKlfn-2!cDMm6YK-`G4Jv|?0X;fy|1uC`~kT8;F32h_}{_0&9HAX?AxNS zqdaj7?ArqSz#ZfAR@k={_H9+z3BDWLE^uDk6n2WIZG(N=U>~?M+_D|^ZHIl^74`)` zy4}IP @3g4{Tx4o`#*TIoep-M&%nKem*9SZ2k&yQi##9qOZ*D%r95&stlAB$ zb}Q^FeiPgcaPc1~> |nq1eBA%wS8#vGBR_GlKY76? z4$~uvfB#8e{H-PSkb^N^cnDo{2wihXUDQ5B*L;eu`BYuhz}*Lzd{|x74#SYcFyx53 zs2xGq96{HBtIFl0=$fPGnxpEX25uKPug}y)?K5=EXXqMmHM!*&y5< #_&=}J+1mf?feQR|EteVt>S_N*$^va9NzSe8`Zn|H7hNjX!7%|&$edeyp1oxv zQT|71<;sz5B=rwgyyw{*k~)Pa95@Olsm3OXIM+eT#y8jY1kXlpz4G+}hW`nSyIQ;Q z^zM&!e#!XYZ;#it_1FFcg$;*#(OxKLYwWcR5miQd5rd~_4TOIK7Ofrp{rO0{D4{OW zNyL>HTn0~j@Sa*;^$eVXw99nparHqdNGB#SdYro+N9RGXK)oz_9Gw}(n)HK54?U7j zC1>fGt$G}tjbBo;;(v5{9G#QXqX <6VrWJJM}3`0g9%h{t>`Pfd1N^4vYaZfJ`6- zNCqwgSAb7}!@zD}D-Z|-1I>XHRE?UC`lb><4^aL9sImVAatQboI83MfdlA?Uya%iS z768+LG=LrrdH`NP1Hc=geehs_j{M&R=+v+`kO1@n=v1*A5D#<*iXgKXpfmbKz&Ica zxDMpf@wo{BdJ1+NI02jl=sf*>fDYWrtLP{)5l90119U`54=r+m$-op~DnJiD#sd=o zI{N+rpvOfnfEl<7-QNJ;0@r}=M06uoKkpQRr-3uT=fD@hm%v$oe%-$c(36M-z;qxF za02vP }-WKY)3NqsJZeq+$j@&syl&-F(mx(9xjupy&gDo?xwDcp{pQ0F8>T zfh&M}BShd@+fu}Nvxa%}SkoI(WDB(y1E`{tfQbNAoD9eYsI#ck$U$i;pyvj)0Xp7C zvyl_1$G$Y&D*-eW(2ypchPx4_QAr~bBQ;Obr NT+09fa+&};wVfqq^2og z#8LHpfb#iCmUNN~#T&9TvV4FR0OIr10;ztj07HlrQYNy;s1Pai0SskE1&uh8BYT5^ z|5i^J4Z~*%hwDn7E`%yXb7c8EM$1tr(4lby%-HzK4bZ& zGg@FUWCj6tpu7_L4?$1hMc@TM0lETRfX+aBpdAnkbOPdl4nRjBj}nRL2E+pf-yPu| zKoXD)3;^JbQX cab)GWK0M-Gdm?}=`q<9W6 z8+Zkn1-uN9EXgeZ@_~84T;Nq;KJXgw4yjp*!0SK(un-Wyo4^|Y2NnT^z*|5uPz1Q? z{4&s`z+zwtupC$ctODK!Rs(B+jlg=~U4V3K0H~bpWX3iGwg8)f_km3S3A+IA>0#ox z>S4-g$WT1t17Ihx1K17h0`};X;;AJL0M&tg05yFz;;9+;10Mn;M9ofpO=0S15 N@@p(+AK|8GMAje?qpBY!CdNV$=jegx14lzxTO1}JYGfPO{LuM7Is zQ4i>fd}Jia8UABblv?h5OC-=WTI^`Wn(zF!75ko9jaNfkwf^RZP95?Nn%Kb5fRNCD z2&uam9mIkfglOC78tC(T_1Ii%n)iMao8CGgJOC+^Mb98+VXA1nMnX*|Oyb8N7GgDC zD4o7+?&`pA({~|TNI+0P5awSI70fKwtO}|JJ5Pt#Nl3hOw^AJz6CDnT5bX0aA;7#u zelUx-8t j==%Jq-cV;6U;(;JYT?gZS}_5SKZE?t2oxg?w}*cnCItF zOATdlVoeC!XsuL9-3dBrE|8ILihI;=X|ofC*I<=IW+=n|)y0NT7SD!>Jw0KQ@$TS| z$w$8s+pldgv7mt9pnx#s7OlcylZSAP1o0Os#I+GqK&-}ltiR-3w@vLhW;Epr2?!0R zrrHF7aN`xmcAKu0wEWgL!^DCEY^qk{t;a!&7vFsU2d@#7H9Q~^ (^@UQKdg>Mz6- zRm6*}QGp+-h HC`X?+tPG5^J D- sL _#hJC#{)` z=6s@LrgwP_F{ADOK{1+V8GZ1~S`HM+FBl~}vqHKxk>U=#_$gISxPamJR^#36fWFsO zf0*!SDs^t_XLPRSV^-tM?oSR*eB^g_LLU;xm@VtT@>L8i-;ajxY2(joyb^vWGRZl; z&Q%u^3)Q{(NvG0k{huADyw_WpuXqTLv>Gp4hx8x(PSngvn=u&&2Sm~w+EoO%!~CU< z4%4N2qNp7UG2N;sPPSvit;YN0o6VjOta|*5gW(qMPep7VAWod^lR4nYEH&kGnCPqgB0U+cI%N> zrv&}ha34}=5F+ #IeK2$UtNsr2>ZRoTo9}ACjFYD-EB*y zMO(|tXeaiQfbr^m)zKNNK6+)WF=h#4jCLsMUB!U(Rg%>GwU0_P`RX%dJrv)e@s5 zruMPLYP{NC5STQ-$@b_-6ASRs+Fu{V+LB?q>?1D6VI?T MEvnM%z4pMa&VKAz%!$^3$k3ll<=tq$dZktMKYtRz!IdpDCv;_EzJ&0|RRNz4hhc zmz&d!p>Cs$DgOyY?8$lW$uw=W{+r!KzOZ*ItKpLq$$v55PZY`VXk_EH@;eQ;P92)E zYKMuv+(g}@JBvgC1lV@5fye<-JP 2g)_Rg)l;jIrDwfk~-x07=Gyf z3L*~z;l{@m0;A`-G=6XqTT*p+Vh|diUs$+rRn~|14u7bos544O@e_7v;=3NKmbrIR z?8L=a-C11`)RR^I&*d%=Jzji%xtR6;Q@K{-I}te%r9XOXt6K#XqiqeQy8YtJi);j2 zCC2qa!)O~xwpolpZJA!_6+N*F(6^Yz2O~P?FLu59aobC(g&{OGX~Jy1kb9vmV?{LS z)0*^2?bheCA9N&({m2z=yo~ ?be754H_ft >VtXR_(sc+agC#2sOlD>)<9jY zn~C_o=$>z}0OO~!bAxZcmA}n;)VQpQ#z!=6-5*jqsCMNrr0Cs~Cl({CXM414ZDg&s z{`BcKE=xv20K)?^(j%KV-51k-b#bR3YH55$!|%{Lz5T7DurI-a8$w%ltr@Jw=Qd*N zY)JFi)OQL5^zEqH_!gEbMx(jd1d*2pvPdik=V^R&qs{KQ3BT7JhF?gA+LMvuWE!TA zyOH8Y$K!ekpMKbnYjrZndNE}rH0qIBX-{P{5_Os7qIfiN>=22GaE?SVKM@uhpYeE5 zGj;TzVJ8Yv6>J007*duv2mv-$TqLqX?4isPg WHi)7QwVQkV8r8pm>mk&6YO3vSg8hsql$uQoPrn)he4KFCzW%9H~#rBVx4y zxUt9NLn$x3M(&A~5erGs6cIcC^?Y(R7Sji?u*YQ_AHd>t^-t+#nBtPLHXD=nl4KV5 zl*r==UXyA5Y(-Y%<0F{n~q(2VzxM{Q^JWo~T(pm*B87?Zml(*ls?l@ae@NMT{GS zR(n_M$C43le9NSy(){a=+4I*zJ&j*XP0vr6C|n1#j%DpSW-#kh`|0ga4WyqP%zRm6 z@#|o=*lK*GBxay#@0UMS{;KSk^KP**1vN3gYqI^(t)HuSeCFrE;5^vs;xTDQ^&Hi? zr%t_E$qNc+mZi9f-;vd7d@5z`f`?0XHmdo3nLt+&I0U+lZ>v-s|M{@=lfRhC1dOk* zZ1r gv 2 zyJO2ze(oXKrsB7Q@dcafi^l(4Y`?dsOu}7crlMZPCvSMvtwy8f7tJXXFg}WN@LIvz z+VwmAT9(pN>_b+o@fDrQHDg-rVs9KT6UY&FAixfZs>3lupLS@UM>@9EyQ_UvmzOZW z)QLY_y5|rt3`Z@E58J%bB}RGs?wp631f41ciR|I5MWg6`+9{jy#U;1)aTnd9Z_`2s zRkXTS7Y9+6>0Cc?i8LCY#;Nvt$Ap?MpYLR1&FL%zR|VlV0(H~mt N;IMJV6vt{zHO|V)eaR1szVp87X>R8vfxYw-=71?GrXnq~VEG%hUMWPT17>Z~Z)V z%Vnb#Y|_Id5ik;tY<#Gv)7A8j%@6LRor}Ie8Y`H#PMMxuSh1=c9f`@Pxo}Iv`Qejm znYLE8j>RSmk3*aTOa2+qeT7GUnQG%}K$+8d$C5S8Pn4zT%O`#}&?ZGQ*z$DQTSSjh za8cu1KtF6qUA^q{<=EI8RX4sSl=;>vx9M}b38aJtgwXc#$7Hc=6zkU7_?}Sc>q93W zT)JYKiLJ#WL)wiPUmW@&DkbG;NOu`|X+59KVes!Q|GaB>#@eq*#-uiTD zHCX&0?VR=Tf~_vSPjsTFn9e+_yhwilz_oC+XqnDBh>Ua=;_+u+^+Aa9<%sa(ET{UP a2XvvUCw6|2&N?%(H-pJLV>8%-l>Y(eaCDgf From af5548db78e32630c00f789ddf297f7e7f5ff40b Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:42:37 +0900 Subject: [PATCH 008/131] cache token (#476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 close #475 ## 具体的な変更内容 ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [ ] 改めてセルフレビューしたか - [ ] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [ ] わかりやすいPRになっているか --- web/src/firebase/auth/lib.ts | 44 +++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/web/src/firebase/auth/lib.ts b/web/src/firebase/auth/lib.ts index 2ba1fdcf..064eceb8 100644 --- a/web/src/firebase/auth/lib.ts +++ b/web/src/firebase/auth/lib.ts @@ -1,29 +1,29 @@ import { getAuth, onAuthStateChanged } from "firebase/auth"; import type { User } from "firebase/auth"; import type { IDToken } from "../../common/types"; +import { app } from "../config"; export class ErrUnauthorized extends Error {} -export async function getIdToken(): Promise { - const auth = getAuth(); - const user = await new Promise ((resolve) => { - const unsubscribe = onAuthStateChanged(auth, (user: User | null) => { - if (user != null) { - resolve(user); - unsubscribe(); - } else { - console.error("getIdToken: user is null"); - } - }); - }); - - if (user == null) { - throw new Error( - "Client Error: firebase/auth/lib.ts: current user not found", - ); +let user: User; +let token: string; + +const auth = getAuth(app); +onAuthStateChanged(auth, async (u: User | null) => { + if (u != null) { + user = u; + token = await user.getIdToken(); } +}); + +async function refreshToken() { + token = await user.getIdToken(true); +} - return await user.getIdToken(true); +export async function getIdToken(): Promise { + if (token) return token; + await refreshToken(); + return token; } type RequestMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; @@ -48,9 +48,11 @@ export async function credFetch( }; } - const res = await fetch(`${path}?token=${idToken}`, init); + let res = await fetch(`${path}?token=${idToken}`, init); + if (res.status === 401) { + await refreshToken(); + res = await fetch(`${path}?token=${idToken}`); + } - // if (res.status === 401) throw new ErrUnauthorized(); - // if (!res.ok) throw new Error("response was not ok"); return res; } From 18bf215f5cd154f28d9347a9de99092aa6901ec6 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Wed, 23 Oct 2024 23:45:49 +0900 Subject: [PATCH 009/131] =?UTF-8?q?HOME=20=E3=81=A7=20REJECT=20=E3=81=97?= =?UTF-8?q?=E3=81=9F/=E3=81=95=E3=82=8C=E3=81=9F=E4=BA=BA=E3=82=92?= =?UTF-8?q?=E5=87=BA=E3=81=95=E3=81=AA=E3=81=84=20(#508)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 close #481 ## レビューリクエストを出す前にチェック! - [x] 改めてセルフレビューしたか - [x] 手動での動作検証を行ったか - [x] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [x] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [x] わかりやすいPRになっているか --- server/prisma/sql/recommend.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/prisma/sql/recommend.sql b/server/prisma/sql/recommend.sql index 2d7b3839..beaf2c52 100644 --- a/server/prisma/sql/recommend.sql +++ b/server/prisma/sql/recommend.sql @@ -8,13 +8,13 @@ AS overlap FROM "User" recv WHERE recv.id <> $1 AND NOT EXISTS ( - SELECT * FROM "Relationship" rel + SELECT 1 FROM "Relationship" rel WHERE rel."sendingUserId" IN ($1, recv.id) AND rel."receivingUserId" IN ($1, recv.id) - AND status = 'MATCHED' + AND (status = 'MATCHED' OR status = 'REJECTED') ) AND NOT EXISTS ( - SELECT * FROM "Relationship" rel_pd + SELECT 1 FROM "Relationship" rel_pd WHERE rel_pd."sendingUserId" = $1 AND rel_pd."receivingUserId" = recv.id AND status = 'PENDING' ) From 2370cac3947b8e8781e6e7feb6eee397576bfc66 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Sun, 27 Oct 2024 14:01:16 +0900 Subject: [PATCH 010/131] Fix zod error (#506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 zod エラーの解消 closes #370 ## 具体的な変更内容 ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [ ] 改めてセルフレビューしたか - [ ] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [ ] わかりやすいPRになっているか --- web/bun.lockb | Bin 118736 -> 119070 bytes web/package.json | 1 + web/src/api/chat/chat.ts | 19 ++++++++++++++++--- web/src/hooks/useSWR.ts | 22 +++++++++++++++------- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/web/bun.lockb b/web/bun.lockb index 3a9e6d19b0f2885328bd7c3021e327483a07031e..870ae427d6ecd0b62c08df27cfd69eff83abf329 100755 GIT binary patch delta 14231 zcmeHOc~lg~*6$h_WmH^27!X-p5D{c`K#&o4Gw$063WA7=qC7 UVF|ty^`gs;hgt zdoHZ0wR&Bxcm2G7NERnMW%)NQC@M_3zv+aXM|Q16PY&$ccs6;*l4bLD-&lNsNpyHF zwYuq?-FBrH)G&@tnx2p{ElHY%d^<_XqVl@nG9F;#nCas!NsiE_BqbzH^-WG|0^I@n zuGW%N4?G{-7Muod1a|e2q=w)IVDe@Qd$LQLnvs&6CP`l+pY%A`lb<@il2jY40~^2( z;mjU<9Snb&S!EC!BV$TZT58JlB mWZP zVPXn}bQ1a0z(Fw8+Z3R5bRpOkdeW3BsZ)HD(sKiqdg*exkCYV}q*Q1PZUDo?aS16C z;Ltw!MZ>;hQWC~K `rY< ZC)QxPVOQoQUiDPa~O z+@{E%5vtU?2BvX640Zue0M`ePO-UV 0H%Pqsrf?H=c(mW)ciO#zbBXmAXwF1!8B8~)cl|NDSmH*$?rKZEuDRe9+b5K z0y$U;rVh*k(>XX!F~}OImPe|3+kX5V+h&2QhAGj!115i|(TbaBl{ kb)gzs?--cojH9+D$PWJX)t}k z6qW~=%Kc$Sv(pSr-3o#~il!n)lAI+;mlaWzVc1c_cG1Fe{>g9c1{Jloo9d}=4+}l_ za51s xDdtS2&pTE^Q-&16>eAr2@{=V!z=7f>>AGr zHXFRK$MuyvRpDh~i?}hwY `jJ5XpzuragWxayu7{Hpu;|htwd?p5CIJv3Jqb9$xsaKHMwqnW0QdeO42B3 zIvNf`BD7Gs3
yS>G93WrRFTb0Br*d7 _#- zC)w7cmC2B-YVv^Uw?OMmoiY3bi2{a4FO$I!`!Y5hgpa|}PlgsrzSt379%0ruM7Lr{ z*S`g+14+6Eq5P_Sq&_T6l7`7eOCfcVD^29lk!Jlz@E#$T`~+!|ER716q!BzXGQtLe zQmrM%S+1r}f;3t-D}$sAR97rbb)ei$Y&y^BWM;qe@=j*MYZwli6s%N?)-qMY8f|RS z{|K!+tx*=n%cIPO6 ~1!MV|S->QsVhWlVJul>M!Qr!^Dbtd3Uppb>q%GA`N}8 zz^OxedG)P=MoR{++L_oFyu625U#BNEdZS0Uehj2oSvm@7q%3*jOjb%~LyDI3K7*v# z1z|)J e2pb4W)e#oWtOU|n+04DSBqhnxd`N?2>2pX{S!&luYhnQ;*^A*W zBuu`tu63S$c}#CCeVpDj83;JY#P;&?-e&eQH})~>f5AD>mAm!{*LN5oPb#D=NIm7e zlaQ3sx&yVm!H_Wduv-a9DZTlkwACO<>MfT}fuwlZ2dS@|XFFIc9SlkJ0!gu}grxXu zHAKss1WEC?50X-YcF@;&UY`gylwa**wt=dc+=QgKYGqYj8Ky&0wwj74lVLYBoP1cR z`N1Z`J!qIY+LRnjtb~^jGP6Ipaj==q=g~+% vgnG*B??Q@K&emA%B;5x|J~a)A zamu!V^=A#2FEcm+acX?Zjl;}rG>=Algy#%1>-6#b>aa*=<;G~Ues#Pg#mP?pfYe>? zT;~Kxa(A##=@&p7DqGxyBySh2H_wR)&uk*eS4gBUvki1-KnFAh7~l%fQJwYBJ>^`5 z$-afE6O*6TKn(zwFWC<+P%`6c#3zh4WRh=OapZ%TeBd;c>tcZAqdJpMoS3o??2GbI zovFMdKz_0B$Oo|t5DnA<;sHA1alb235J{xrAf}4A{>eNROdU!F=pe2MqykiLDnRAa zRedIy>dyk`Af^WAl7fSn>MsPS{$jK*8@vyN4q`IM1;}76Kn^wobP!W{AwUgnSM@z$ zI*4t7{iNU^Ci^1*b@Uiu0L}t5QWw#_+`#8xI;wLG=+^*h=sG~hzr$4j7C>AHke@G9 zz5}KslZ3j!_oU$ PgIG`V?*oAv4p0k-NjIrFaYN`m!Q}WgHJ_N~dLWqQ zbU2ubMo{t>OhvIsXl;!mh2~!tRHlNls)OoW2l-Rf{Qrw-gr>ocI9;``&Xkpbgm?xL zYF`$pVR `NWs=>ReL3P$~ zyJR=Hn_RV8rdknGv&+HMOpcmgooge1y_#R0DQg1~;*Chi&t@?B*{bGm15+3~m@-Tx z$cDSXbW~@mxJS(=rWLdgO!)`Y{OU{%9#ZqGGi4n{LiIlZlYW9BemY4%g+^=oDwrnh z7L}?@k=|2vVruwXFg5g0%_k=DJ0uj@k7_ R7W^wrrirROF^Ne?)OAKrGh~2XVp^Ql?|^zy zh3dCHRDpEru_q}wh^c`Vq~IVXM=#w1@z~Lh@;>+Krsvg7RlBS2sQKW`JFyI@7N4 z>ZV7&>3Vh3Q*{?a7Z)5a?u!1y`m38B>A!l@gQtD}e>Xiv +!W;elJVQ*xqsGb;sxKvGYE2Jb3Vqe;YUV%X=zqS6B)z zzB%ug2_9D51~KVbeY!uN#> R=Co_-sX>?73A9S*jx)+$hYKL`LI=X+-{Wx?=hlRS-EYV9X|jq zn;Y`1{3B=+^DKB>Qvz+=YCG<_+QOFd _rLRSN zYb{LhjJ1d_AMruU;okX(4_bD cXbaXMzI7Is#}}?ceCrV(v^6|zJ>r9wx8B0? z`8{YWHXyzY7J7lS0r71_e9$)XUK
?)?GcgO>e)g?-G+p)Dvxe1#TvfG;dWeA^Kpv_m`$bNe;4 zyzLfN%I`s2u> %X9wcjiTI!$=e>3!K4^tIE$jq;46R@n;@f3mWqivn#J3yq z?Y7`$K=f|JSA_VWo#loi#0PC+k%gV-CD6w0L412G>>^LzgZMr~e9-Wu^g}DV%qJtg z!po3;#y$30+5hkiq@VMPNXxl*F(NBQWW^S|1uHMMvg 6g6M$5!?g&qsQfKmHie>_;^FE$klOvLDeLKr{y| z>^_e^U}fL(T}U5r!$B*1$P5tra*vfw5lac<+%MN3X zN-;;J7QCLxD7CWR_(i0Tx%Uw(`<=gm^a(FV`Uej v9MbFF|>jc2<3!@8TghH2<0R~ zIcZ^ac=Sn3Oc^EynjJTkVPc?7EVHosyad{~Q<#`j7S@m_pTfkP#>7Bt%#Ek9@Sx2; zZD9_)3|jgb1a-#39C^kWEWEQ=c+ia8`z)phTJ~8BbK&LC7M#PvJ7;08eBn7Pyz^Li z(A;>~c`Q6=dFL(6qv+mw$2$Cbd;9mU+1O^`w-p{IWEBm#@~w_vXk^3xxM6F%u>*d- zBbN`jl1^6Pcub7vW@ObuM;V>FDb@I{#!aEK%S9EpmoU6(om|=2_M5&hd|qZe{>~YP z^~06-%JMZC3H?O8Qxx^(^-vyvC7^b^8)e`XguC!+#yZl?%(LgpLD`P-Rw}>QCiR6* z*YrV3P5IFwiS(;CTFo=6_2?F{lbYwO=FyMQ?f@MwY99T>?gFRs%~Vr0lb+$ctyXqb z^XO6HO@NMOY92jSyaiAhg+o>7IZq`(W$tR;I1+Gt+d|>m5z~SKBi)1D(~@T-rH*L} zNsM%3u>lS+qUV_Odzv1V_yBDIU%(Hb2SGmpKLfu2PsIZ_=BB6Mdy9td%%xr=#7;mI z(3y!2?rgl@FnFbR03iUqXYdB-9RmFTZwb(^e0oUZ1Q;c8$(^-v?S|HR06l?T06iTD z1cHEIVf0|b>v=<@d##2*Bb|8DgLTfV2bq4gaWqfQN0Wi^KngGkNCo}|Oa>Bxk-%-> z3*aPB27C;B2+(hzAfPRf2%j{FG(!e}o<2VVbdY}qp8!tMES5ny2owW5fi1vNfaZ4s zK<`(Y0&aji-~rH+l2HIXC*2Ovqn07SPyic*{3xa`xF66TSOuFrfF5kF0MdY|z&&~n zI1>(SpwKIXbHI7v0zeNVcLVg0lGYYIzZnjU0Ac}pu0v0JX9BZ;*}xoto)%9BUI*xT z;RAr4wbuk{0e9g0OW-Tuu83;R9N7aA-JH2({tt5K5%1ptdSbg2$O7H~-UR5m^9|V0 zs;2iE<-lU((c|Fv0Q!ZM0nj^+C185ZG!aY>()R=Oq< N`*^}dUM5#AxoJqx0L~C*puo%b&{thew-USMP4Zt#h1MdMj zfB=>Q^k8TOkPEB?)&r}62CBXmyavbvRs;FKI$#rpxDmo;U@Nc#_yE`rkds1y8lsbD zFF>!$ih$j~E`V(H0G(JQ6FyX#>S;Dw*?#1e03QJ#1N&%*56FU2KqGMkpa-Le02*YP zJ<>~o!vNVB0h(QsX=cgxD4@-_R(1+`WdLmlv|_5SmJI0WX#Yheaz@im&dH%{0H#h* z89AVQtr5yQ2WZaELZ$|)+fe7v16m*#A=BAUp!!XxA<-P(hx|40888SCk9?S8&F5Dj z-W7(ntV<&jzX52L?*Td{;@Yy9%(_h34(otv6QIq2&M0jHYD1@Ol1@>Y3@x8zx=>Ml zx`^dKr^&X5+z4n0kS$HBwm2Ffk0!l7xE^3fuUp7~ob3f@wKqlqtrA*A 2mG>pt95ipvx~^mg#cs1Xxgyf+Ra_)o2~1;kxaI0vr*v0ZC$?FKbnN$Co)XXYKP^ z?K3o;PBuhR{LGKVHDi?`whuG5*FJZA fPYT8Q>RZ@Z3vw`uYC+99+W;1}Q*jP0tm z^2zVJ2R2MO_{)ida*4lR0H#KKHvlbu)sHpOAA(g~ajP$L5dH(0t?Iq0_TBGTDe~es zt;_AHGW-Vk`P+Jn{{5NLUo{-`&l_&4eVm*gdgGz<9*5d+?(Y{wJ@fGvm-@3Yx&}U? z!vGejbM+CE24KY09 wJS)u4S5Cf%* zm*tF9r D-)C&XXs8R7W-lpkVWb&tPVB(LP|lxp!Or#DN>v!XU^G z-f>A2BVoW!ij2WnHR)o2mD!8Z!OV><6ctpmNjw2H)xM 4I^v*^55If3#Te9 U0#H zqOoW^JBpZSESlpYI~pNsUwA)mm^9&8@Yx)61?RJ0K(KUEl)`{r7uP^kZT|!`SSjkq zAU9ih$6%i2-NZpmiD3iT6>*%hD#ha%>@&&+#>zwj?Jd=I9kG2lYgD!8Tto4TJx8hJ z@@bT^@(cUTIG)gH@htR7-P`VpfD!1-%Uhe6GXmqIHgRSI>#lnLlUp6_C9#NHwOkd; zy8p>U+bd-YpMUJ8srJdaasIF_uKKvk>YQV#ywokn8Ueu_#g#a1lr(qn`qDmoR!knr z`skdxio+wZ(P BM+87TT3~=E2?-zs9rN zrrO8nk#V~Hm%p>UrDN)uBem= Z>sF}igu&0qiG+=*PME3bjrCObyWu1C-g-vZDReup5M5tq)87k zXB0Z2eROZ~Dhv&La-n;bMU2=5=h#?IgIu(a_7_aa^}4a3mtB?JF7ccUw6FZXifU>5 zbC<5istg{AphT=sb(e`2xrs1u+fTXZ-Mo1yZ(HDjFdK$J51^eTQk;iDi1royz{m?e zO ~q98^iz@uzg|z*=b+q zCznJN1lOGSRh8Xsu?7b1wNLriZJ9Ivp>4}cRR-F}{@c1I$4)VOt*R<M;5c_I}CA=>{Dc=)%?$7iox zP-UR~W1;lRr3H;0d;D5eazGp)-`ZasY#T=Ue8iTWsWP}D9>G8tFig}PgU&__lmE7o zb-KvyHS_phb8!yH+ir+7N|?vMllG^Bw|Yfd*8jBNsbWA^f>k1I4D&H;frke8$s=!w z4tl!A%@C!i$~uc{!o7BJqit3k4U9&jRe?0av?T;Au zw {eQ6EuT(!P=Y z+VxPru)Q>kfwdRN_w4v7KT)(z!K(RN3iEd!Z&i9WFEr!~ORx9rH`VB^3&s0V*!EV9 d)%*H(zU@@WH$)`NVlBkZ9n7Y9B4>#a{|kIwz4!nC delta 14067 zcmeHOd3=q>{(k4k!9naHkwmC9A(4~C2?>YTou;uL5ke4zphR0io9Iegw`DG4-?!M) zP|=oF7fVq~tG8$s?QNy@?%LA(Jnzy(Ki&Jezkh!B{p5L{`ObXjJM*2HciwsDOqQ-J zcWha?XCp%QR=RLl71Vn>G5FP(xlc43``*!eKb(?Rzh9;N*3(X K^F0k_MMkA(kAGmCrk!nJ6zoQPMa1W%kIkZ!b0Q7< zGr$!zua !NVyA|(@<0;aFdn>*8#Ilc7W;X7^Jh!H#Iga<+u!2AArk2i0>F@ z NiVvy8=+yu Z_rfg`Y`+(~~KG#{(8iMzNslN)${CVI8;50D1uoIXq z69#7WDuZe7S|>eUZr%k1*!K61&z>|aJ6mgTk0FmsA2&X8xTa-f54|rlYq++hhf(iE zU=HUe!1Vh*Gd|sv?=jO8%=o5ee04CJ{&upF?^AGXEmzAsjsO$(fN6MxsW2bRhI-VL z$APJr3Z`CXFrU9DGd;*mFK^0U!x83>#Q0#xS%VGU>lGi-ACBfCp$`I7Y?WdZ(7@zM zU=E( Ipc K zYA>$@#EVw4ILa+1%Ft-H*ex@o-S!)pF5~59 >X^ z67T4X5QaydkGxUSCDzE|7`NjF;?RSr)Qu)C(N|{1x*e}YXxa$G30*H#79%dBg{EN$ z=h@^0r^~ScQj8(haXBtQ!s7>hXHAzQ93!DEq;k5&MKaXob{s|=CLN<>M_J66m@)F~ zazeDrF&$EOJ$FG3m*Wtmp^$8B5Jz~FrePAJ ~xrZhIxnp$U3J zbwwyqUWto$Y(faCuu%}`vR#grpS5c1h(vv93XY 5puGmr;Rw)a2{(N}Ttww-bEvzl$~Wm;)ke?2J`p>}#Em&|YL zwm%H(339AEfwTEIU8Q-vrVZ3XGY~SeT|-FEW^Vy!hU=*kA)}=#V|1D=RnH~b$>Mfy zu~&w+cROmLJ=iN~5;RwuDPfq_aM^c5>dav(%E-_}w__+qJcoG|c?11o4D3ya !G#t>(P?E2^%0f|Ef)r)kA%`&vB+kO(ZQ)PJ9cza}5 zO&g?#W+60K4_!vcNbQ2BIz^9Lju7;^C-@+cu1BSzWi$2AeuRvyjXjpRM-UpRr+$V| zh8{}3Thn^$p_K@E_0Ua(j3Rm^nN}PH2%*P~ac#StBsbiRp^pcgeIfX~i98va z%p|w{6+8nS<=CWnyH8KOPkSYZo^o_AH$@}iU4**oDs_8Nh04PbGE&zgWTak4=x#kV zp|=(HC_?w>aVHQmQtS5l$qGV7-c1M@_OAaF7uVOy@hCz@xo;xWUAI@WpOtqYLWbq_ zKgC@~NVn%`hnI;l)i@Jfj%kqatZ|^mxEzI$&|jRC{9NJ@8QRA!_Q_1#lVv{cOJs2$ zx9zhO8P>P0z109so2WlMZy*%MkYh@!F$ F2V2mMTa0YinyVP=AcG z2bxd3y#S#hx}D~OtY`F5g!CuV5uRqu8g~7u840N+EMgAv#>=e!2_iz~_jhB~DDLmJ z9UUygQre38GBd?(A2meN(sZ-?5bCT~uTnZfdWD>gUG`p(`so_$5z;3NQA-vNh|l%Y z^!FR?cd`%U0Kf(W0s;sEc$H>5+ =Vk1Fv6XI@2F02MjjLM?ll)$#8%d znHe)o#tTue&?qo3a#>&;z }31yJFi0538X z76MFP1~7dsz>CcEO#lnpY|4dTUgYw?ixhB?slOLsMPCCPK<;7O*i^>=7Vs~CS7~P7 zod#IY8GzTHVdg&zkk13ObHU(T?IQ$ukt+e$DBvPf@j3-ue}?Jd=X$Q+W3)dNnc+(_ z1DQ?r9l-b>0JcmS)P@!E!Hu4jqwp(C{mMvZ{;FWgwN0*TawD)l{up2%hk;peBoj=g z+!As%a2GHQb~Dq-?Bkwb_T>ODlTvy66=u>v+*n>ZnBz|mSj>bGrh(F25%Cku_}^l- z%p~ZMA29VxGovQsM$QrD^M`<5qlXxat2A?PK5WL9W=1_?#*^7HkC`%=`DTJyk*C2- zn#J249B(B2!&G?2R4C0fJll*XGdPDg*=a~fE>q`1Vlnd>`!lWpd8HX∋hUH}Y!S zXlE^$cJj^m4Pd&l1 ~y=|rp*85M7V8%i-fy|M!3(WYJ%=pqwLwn5l(#)urabx~B zz?Apl#%J>cm_zv#m;>dkFv>?j|Nhf1f_afy@Fg${x?;wY8N7-cJ^S2@Cv$||GG%gg z$W@s6w^(og-%|KJ1Aii&4plexEzV`2HWEV3g#TmC{jU_vzHebVL}p*dfZ02-U?#bA z8T}t;MzjW3K|(jC{ti>cgBuI#38vf|Hx|?%%z{!FX)?#kAjs^3A!fYJ9DjF2{7k0q zP%{IW!Qr@> ahVgUTr^$)WDt>q8Pzhhm* zzWx*IA7 mS3=Ut0d4yg5R4A5!Ok zYalcQ7eoJB1L+O%Us;mS@~^C|{=ct*V$d{ydJR nq#L z^N0uJ%z0ipW1g?P0x3tfQeGLSeB~155mRL`q>GS}=6l40a>0DBoIl@J-hz}XyDjj_ zt_ytS<^>+?tKNk4Eu=vUJ=k5#U+9(V7y3%yMIP~(Oj+cW{TKPl-H@J;j>TSCezC6{ zyVxUU%3Y9(AO$b+h^J)c60aP!#8)1HG)o37^-BMxzVgAP9`TGk2 )_uykI0w1AQeFhUhfebW#)SLw;ujM+AIU|;a@)d%lC+_@*t!G zkis{3M1joN0RJ|?KS+f#bR+!R2>&*E#1451(tD8FZ1RYma^@!Zw+a41+9g|UhJTyk z-)4_^Q5HkG2q|fc2frOH*aH8yz&}VY%h~ALu3O>XRu6s#xVaVnK^nBpgT43sZSZd! z{44N?H)Kiy{40QekoHT*bMWsu`1hPg9F)5t6+sFv^oT<;vk?9j!aqnyWWaX#w;ld% z_lTqNAfyA3!gqMYahbCN{_TK&klvP|&wIr?aysrO ?O_xI#^+)v3? zMewW$o)vk-8ChK974OUTyS(B9xd8XG@&@i7%5E=s#W}eG_w({5?iXaw7ro*mnUDL& zQoQ69|CTAZe n5eD+As{ z$GnM-dDA1R$b*m$Kng$P5!GbQA#}_kbPS{#GW0M8-eC;9!yf$PathLWklGyah}v@I z5e&Q|7 b~}cFcg(kN^RbE* zWseViMEAnuXMVJa@$&fDN9=ZN+ByowyG?9NvsdbbodPM|4be517H&TOjPRR?-#993 zS{`2#ytp{pUfA=abvD1TsmAZTdc2vmrO@}2_v0!IsY^Y4C55PlA)-z0?H`ScakY@d zR9_0XESamUUPd;3Lyti&`TCo2{$K{VY;&7&0cIROlu s$<6S12~aJHEoy{Xhw4N z{J5Dp$c*Dg-v?B~6>P?F(aY}&rqLH>;eOM3fN3FS+$aWcU2LE_*Aw?Uxsliv;QKTT zXa+O~!U68Yd;xq3d k;5B;3&Xt!2*EWgd9HH zKp6m}0s{eVtngD~3NRIz20RGxbLBqZet;WxmjP~a+kvvcN3i`d@NeJ~;8WGUp{Om6 zsg#ByFqeC*PXXLAeHO?C@_^|8H-6to4h~%Ypzt0r3vt|! Qay6 z@kxODDo+DX0sjD=0oDPl013=t$+Hod56lDR0{;XQun<@PtOOPTRZV#rcqy >Wtpkgtw7 z4i1hU8n-g@rO0`jZ{?Z*^ZEgNtMYBjw{UI1gM4(7de&gED#~U$7mfs6cV(#;B1G8E za}lDh*y$fB(&~y$Dt({`2(peE-ngr9+OYK1&)dWcjg1w>vBqlCJ;Fc8I;?o{(6Dy@ zy7p`vn@Ed|aK=VNMSk5_or8wZRPG=VAS$RD$s$QKQR&GdS~%6*WHDHDQOQG)) oUJ^@1q@c1wHG@<|?s|h%#E*&mI*K ztGU%PeMEGib!2$e==<->xt$t}JW&zOXjomSK7diNT73gO+u0VXd0*ilXr0qN{rs5C zwR0X?3;mdgNLFH+8r)X|1R7_GaRxbd@Y)v5ugrx4>PUq-O7=znCaV3U-s*d*TPKgF zoC&FVcvyIjS?88Gl%QJlgMxL`_~i*r&RovzUK0wgh^QE}pG_Ttf_PGiF(N?C>nCD_ z9u+N$)Q?E9osCv?ykdl?qo#YI*hDP?1zIPLFIB1+Q1RhLQ7{O1S-~82lzOphYbL0p zs-L2p5ALrMOA%{SPfCeu3@Fe#JA7&M)W;pITP`c9+eWnv3bEFq<2P%4=U1W1jgut` z*0JQ)yPoRvz2ETXN>Y5(6&4YuZm@X07D-lRj5-BcXP6U{`z9yNl6y)ro0T@|cZZFi z95PgFCll05S;F6TIYCuT6}8lp14K}ub=Y}Gw_z3Un%Va>3|oUw?Hz#XtXF49Ybn+C ziNtAX=Jq!RR?V`p7+9KDbx1`A>l1*VdOB6~63f(wjM7JZP1R(eXoWB*9wF6Kk}+GT z0|P~MbFOG$O%j~BDq&4f$FEVf=?!`xt}3h))5H?x {4*76W-~PshWu2?pD6}eC$s||>)#>Rp7svfFA8HT z4r}YulVt5O&X{uQ+F+}d0#qrRKc6I(dTNMB3cAq2I3S;&9DIE7oD(m?G#+gZ+b=q( z%IWAylg*Y@?b1ba(`C>T>0%`4TDqt&2B>O7#Uk5 38<(Q;*VekfWtOnnauw9bFK%k>;FamK;nC3 %jQ8X$u~TS^VaYB?{J& z^0Jdo49|M&3tLIbUiBcd23lv(=S&YudFcMHnwBW|r~)X6hUyUY61x~@&Z}HF?ri?U zp&v^0($tTvgLUY==fbO3+butSyF|e{7XL~6M&-Zi(DC(>lp>Wh9B(XRJ_)i;tv5Tl zYU1%R&kfWY8oh` wNy>-P(FqeL3TnkrIin8=+>66wMsj{q+xk zyfC#FmW*lFPhB4=d;+aA{K2!kj~ljJK424JoSJYoP*p~uUe=-iHv7sq?OlHVjuHiZ z$~DXwb8^kH`fItPnvIMoMW>DGS{B~oKX `jbiU$@cg%0RPVRgau2mmpicVsP3LP!te ;Dw?1=YB{q_~QIi7VEBGF;l2v1aLsWmvgd z>jQtags<#|YUdctpVogqM9EW$%*Z58A}pG-HMPO`58QH({XlzY;tB-+2GnvD+WR z5+`qGjOMR;XNl;R)_*@`&+K}8` J4TIHb`cdM%6k-_@r6#i|f z^lR2%7@bTnD~uIU74GFe0IqFWs`FTM?Bj^66XP}Bc#p=#9u|`;-#%w5#U0&wVyxKG Mc<1x~6r&RU132|!v;Y7A diff --git a/web/package.json b/web/package.json index f07c77dc..53c29a8d 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.16.7", "@mui/material": "^5.15.20", + "devalue": "^5.1.1", "firebase": "^10.12.2", "framer-motion": "^11.3.23", "notistack": "^3.0.1", diff --git a/web/src/api/chat/chat.ts b/web/src/api/chat/chat.ts index 182ee85a..c161e1ba 100644 --- a/web/src/api/chat/chat.ts +++ b/web/src/api/chat/chat.ts @@ -54,7 +54,16 @@ export async function updateMessage( export async function overview(): Promise { const res = await credFetch("GET", endpoints.roomOverview); if (res.status === 401) throw new ErrUnauthorized(); - return await res.json(); + const json: RoomOverview[] = await res.json(); + + if (!Array.isArray(json)) return json; + + for (const room of json) { + if (!room.lastMsg) continue; + room.lastMsg.createdAt = new Date(room.lastMsg.createdAt); + } + + return json; } //// DM関連 //// @@ -74,7 +83,6 @@ export async function sendDM( return res.json(); } -// WARNING: don't use this outside of api/ export async function getDM(friendId: UserID): Promise { const res = await credFetch("GET", endpoints.dmWith(friendId)); if (res.status === 401) throw new ErrUnauthorized(); @@ -82,7 +90,12 @@ export async function getDM(friendId: UserID): Promise { throw new Error( `getDM() failed: expected status code 200, got ${res.status}`, ); - return res.json(); + const json: DMRoom = await res.json(); + if (!Array.isArray(json?.messages)) return json; + for (const m of json.messages) { + m.createdAt = new Date(m.createdAt); + } + return json; } ////グループチャット関連//// diff --git a/web/src/hooks/useSWR.ts b/web/src/hooks/useSWR.ts index 0ce481af..83d3906d 100644 --- a/web/src/hooks/useSWR.ts +++ b/web/src/hooks/useSWR.ts @@ -1,3 +1,4 @@ +import { parse, stringify } from "devalue"; import { useCallback, useEffect, useState } from "react"; import type { ZodSchema } from "zod"; @@ -72,15 +73,16 @@ export function useSWR ( const result = schema.safeParse(data); if (!result.success) { console.error( - `WARNING: useSWR: UNEXPECTED ZOD PARSE ERROR: Schema Parse Error: ${result.error.message}`, + `useSWR: Schema Parse Error | in incoming data | at schema ${CACHE_KEY} | Error: ${result.error.message}`, ); + console.log("data:", data); } setState({ data: data, current: "success", error: null, }); - localStorage.setItem(CACHE_KEY, JSON.stringify(data)); + localStorage.setItem(CACHE_KEY, stringify(data)); } catch (e) { setState({ data: null, @@ -92,7 +94,7 @@ export function useSWR ( const write = useCallback( (data: T) => { - localStorage.setItem(CACHE_KEY, JSON.stringify(data)); + localStorage.setItem(CACHE_KEY, stringify(data)); }, [CACHE_KEY], ); @@ -115,10 +117,16 @@ function loadOldData ( const oldData = localStorage.getItem(CACHE_KEY); if (oldData) { try { - const data = JSON.parse(oldData); - const parse = schema.safeParse(data); - if (!parse.success) - console.error(`useSWR: zodParseError: ${parse.error}`); + const data = parse(oldData); + const parsed = schema.safeParse(data); + if (!parsed.success) { + console.error( + `useSWR: zodParseError | in stale data | at schema ${CACHE_KEY} | ${parsed.error}`, + ); + console.log("data:", data); + // because loading old data isn't critical to the application and wrong stale data may cause several problems, + throw ""; + } return { current: "stale", data, From 6a4fe055400d25ffef05eff2ef408e450d2bfb1b Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Sat, 2 Nov 2024 00:10:36 +0900 Subject: [PATCH 011/131] =?UTF-8?q?prepare-deploy-server=20=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#505)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 ## 具体的な変更内容 ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [x] 改めてセルフレビューしたか - [ ] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [ ] わかりやすいPRになっているか --- Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 9c75503f..72b7c243 100644 --- a/Makefile +++ b/Makefile @@ -45,10 +45,9 @@ test: dev-db prepare-deploy-web: copy-common cd web; bun install; bun run build -prepare-deploy-server: copy-common - cd server; bun install; npx prisma generate; +prepare-deploy-server: copy-common sync-server generate-sql deploy-server: - cd server; bun src/index.ts + cd server; bun src/main.ts docker: copy-common @# deferring `docker compose down`. https://qiita.com/KEINOS/items/532dc395fe0f89c2b574 From d21df4d3529efb82e214468f4a526dfd5c918f19 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:32:47 +0900 Subject: [PATCH 012/131] Scraper (#487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 closes #486 ## 具体的な変更内容 後期学部はカバー 大学院はカバーしていない ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [ ] 改めてセルフレビューしたか - [ ] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [ ] わかりやすいPRになっているか --- .cspell.json | 10 +- .github/workflows/rust.yml | 32 + biome.json | 2 +- flake.lock | 40 + flake.nix | 23 +- nix/prisma.nix | 12 + nix/rust-toolchain.nix | 5 + package.json | 2 +- scraper/.gitignore | 5 + scraper/Cargo.lock | 1907 +++++++++++++++++++++++++++++++++++ scraper/Cargo.toml | 16 + scraper/readme.md | 25 + scraper/rust-toolchain.toml | 4 + scraper/src/io.rs | 36 + scraper/src/logger.rs | 43 + scraper/src/main.rs | 99 ++ scraper/src/parser.rs | 57 ++ scraper/src/types.rs | 15 + scraper/src/urls.rs | 42 + web/bun.lockb | Bin 119070 -> 119070 bytes 20 files changed, 2360 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/rust.yml create mode 100644 nix/prisma.nix create mode 100644 nix/rust-toolchain.nix create mode 100644 scraper/.gitignore create mode 100644 scraper/Cargo.lock create mode 100644 scraper/Cargo.toml create mode 100644 scraper/readme.md create mode 100644 scraper/rust-toolchain.toml create mode 100644 scraper/src/io.rs create mode 100644 scraper/src/logger.rs create mode 100644 scraper/src/main.rs create mode 100644 scraper/src/parser.rs create mode 100644 scraper/src/types.rs create mode 100644 scraper/src/urls.rs diff --git a/.cspell.json b/.cspell.json index 98a900be..1f059cd5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -15,7 +15,9 @@ "qiita", "safify", "supabase", - "swiper" + "swiper", + "reqwest", + "chrono" ], "dictionaries": [ "softwareTerms", @@ -31,6 +33,7 @@ ], "ignorePaths": [ "flake.*", + "nix/*", "**/.git/**", "**/node_modules/**", "server/target/**", @@ -39,6 +42,9 @@ "**/bun.lockb", "**/*.svg", "**/migration.sql", - "**/data.json" + "**/data.json", + "**/Cargo.*", + "scraper/target", + "**/rust-toolchain.toml" ] } diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..4e53c038 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,32 @@ +name: Rust +on: + pull_request: + push: + branches: [main] + +jobs: + all: + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + RUSTFLAGS: -D warnings + defaults: + run: + working-directory: scraper + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/ + ~/.rustup/ + scraper/target + key: cargo-cache-${{ github.job }}-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }} + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: clippy,rustfmt + - run: cargo build + - run: cargo build --release + - run: cargo test + - run: cargo clippy + - run: cargo fmt --check diff --git a/biome.json b/biome.json index f16f6128..b2ca8999 100644 --- a/biome.json +++ b/biome.json @@ -10,6 +10,6 @@ "useIgnoreFile": true }, "files": { - "ignore": ["bun.lockb", "server/target", "data.json"] + "ignore": ["bun.lockb", "server/target", "data.json", "scraper/target"] } } diff --git a/flake.lock b/flake.lock index 6ccba3b6..98ba3177 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,27 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1727764514, + "narHash": "sha256-tvN9v5gTxLI5zOKsNvYl1aUxIitHm8Nj3vKdXNfJo50=", + "owner": "nix-community", + "repo": "fenix", + "rev": "a9d2e5fa8d77af05240230c9569bbbddd28ccfaf", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "monthly", + "repo": "fenix", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -89,11 +111,29 @@ }, "root": { "inputs": { + "fenix": "fenix", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", "prisma-utils": "prisma-utils" } }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1727706011, + "narHash": "sha256-xxgUHwwJ+1xQQoUWvLDo807IZ0MDldkfr9N1G4fvNJU=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "28830ff2f1158ee92f4852ef3ec35af0935d1562", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index 7c1a0a42..25c451e5 100644 --- a/flake.nix +++ b/flake.nix @@ -4,21 +4,18 @@ nixpkgs.url = "github:NixOS/nixpkgs/master"; flake-utils.url = "github:numtide/flake-utils"; prisma-utils.url = "github:VanCoding/nix-prisma-utils"; + fenix = { + url = "github:nix-community/fenix/monthly"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { nixpkgs, flake-utils, prisma-utils, ... }: + outputs = { nixpkgs, flake-utils, prisma-utils, fenix, ... }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; - - prisma = (prisma-utils.lib.prisma-factory { - nixpkgs = pkgs; - prisma-fmt-hash = "sha256-atD5GZfmeU86mF1V6flAshxg4fFR2ews7EwaJWZZzbc="; # just copy these hashes for now, and then change them when nix complains about the mismatch - query-engine-hash = "sha256-8FTZaKmQCf9lrDQvkF5yWPeZ7TSVfFjTbjdbWWEHgq4="; - libquery-engine-hash = "sha256-USIdaum87ekGY6F6DaL/tKH0BAZvHBDK7zjmCLo//kM="; - schema-engine-hash = "sha256-k5MkxXViEqojbkkcW/4iBFNdfhb9PlMEF1M2dyhfOok="; - }).fromNpmLock - ./server/package-lock.json; # <--- path to our package-lock.json file that contains the version of prisma-engines + prisma = import ./nix/prisma.nix { inherit prisma-utils pkgs; }; + rust-pkgs = import ./nix/rust-toolchain.nix { inherit fenix system; }; in { devShell = pkgs.mkShell { @@ -28,10 +25,14 @@ gnumake bun biome + pkg-config + openssl + ] ++ [ + rust-pkgs ]; shellHook = '' export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${pkgs.stdenv.cc.cc.lib}/lib - '' + (if pkgs.system == "x86_64-linux" then prisma.shellHook else ""); + '' + prisma.shellHook; }; }); } diff --git a/nix/prisma.nix b/nix/prisma.nix new file mode 100644 index 00000000..563d59d3 --- /dev/null +++ b/nix/prisma.nix @@ -0,0 +1,12 @@ +{ prisma-utils, pkgs }: rec { + prisma = (prisma-utils.lib.prisma-factory { + nixpkgs = pkgs; + prisma-fmt-hash = "sha256-atD5GZfmeU86mF1V6flAshxg4fFR2ews7EwaJWZZzbc="; # just copy these hashes for now, and then change them when nix complains about the mismatch + query-engine-hash = "sha256-8FTZaKmQCf9lrDQvkF5yWPeZ7TSVfFjTbjdbWWEHgq4="; + libquery-engine-hash = "sha256-USIdaum87ekGY6F6DaL/tKH0BAZvHBDK7zjmCLo//kM="; + schema-engine-hash = "sha256-k5MkxXViEqojbkkcW/4iBFNdfhb9PlMEF1M2dyhfOok="; + }).fromNpmLock + ./../server/package-lock.json; # <--- path to our package-lock.json file that contains the version of prisma-engines + + shellHook = (if pkgs.system == "x86_64-linux" then prisma.shellHook else ""); +} diff --git a/nix/rust-toolchain.nix b/nix/rust-toolchain.nix new file mode 100644 index 00000000..5a919688 --- /dev/null +++ b/nix/rust-toolchain.nix @@ -0,0 +1,5 @@ +{ fenix, system }: +fenix.packages.${system}.fromToolchainFile { + file = ../scraper/rust-toolchain.toml; + sha256 = "sha256-yMuSb5eQPO/bHv+Bcf/US8LVMbf/G/0MSfiPwBhiPpk="; +} diff --git a/package.json b/package.json index 8b60397f..d12a6c1e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,6 @@ "zod": "^3.23.8" }, "lint-staged": { - "*.{js,jsx,ts,tsx}": ["biome check --write"] + "*.{js,jsx,ts,tsx,json}": ["biome check --write"] } } diff --git a/scraper/.gitignore b/scraper/.gitignore new file mode 100644 index 00000000..c5903a8d --- /dev/null +++ b/scraper/.gitignore @@ -0,0 +1,5 @@ +/target +/data.json +.cache +/course-mate-scraper + diff --git a/scraper/Cargo.lock b/scraper/Cargo.lock new file mode 100644 index 00000000..19fb391b --- /dev/null +++ b/scraper/Cargo.lock @@ -0,0 +1,1907 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cc" +version = "1.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "course-mate-scraper" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "futures", + "lazy_static", + "reqwest", + "scraper", + "serde", + "serde_json", + "sha2", + "tokio", +] + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.11.2", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.2", + "phf_codegen 0.11.2", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scraper" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0" +dependencies = [ + "ahash", + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "once_cell", + "selectors", + "tendril", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "new_debug_unreachable", + "phf 0.10.1", + "phf_codegen 0.10.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "servo_arc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/scraper/Cargo.toml b/scraper/Cargo.toml new file mode 100644 index 00000000..1711fd0c --- /dev/null +++ b/scraper/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "course-mate-scraper" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.89" +chrono = "0.4.38" +futures = "0.3.31" +lazy_static = "1.5.0" +reqwest = "0.12.8" +scraper = "0.20.0" +serde = { version = "1.0.210", features = ["serde_derive"] } +serde_json = "1.0.128" +sha2 = "0.10.8" +tokio = { version = "1.40.0", features = ["full"] } diff --git a/scraper/readme.md b/scraper/readme.md new file mode 100644 index 00000000..9b1b87c2 --- /dev/null +++ b/scraper/readme.md @@ -0,0 +1,25 @@ +# 後期課程の授業をスクレイピングするスクリプト + +https://catalog.he.u-tokyo.ac.jp/result にある授業情報を取得するスクリプトです。 + +## Quick Start + +以下のコマンドを実行すると、 `data.json` に授業情報がjson形式で保存されます。 + +```bash +cd /path/to/this/dir +cargo run --release +``` + +## Maintaining + +### Add faculty + +ページに移動したときに、左側に学科を選ぶとhttps://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=1のようになります。 +faculty_idが学科のIDです。 +urls.rsのUrlsに学部の名前とその url の tuple を追加してください。 +すでに全ての学科のIDが入っているので、特に追加するような状況にならない限りは変更する必要はありません。 + +### Extending code + +コード中に .unwrap() が多くあると思います。これは意図的です。 diff --git a/scraper/rust-toolchain.toml b/scraper/rust-toolchain.toml new file mode 100644 index 00000000..044314a7 --- /dev/null +++ b/scraper/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.82.0" +targets = ["aarch64-apple-darwin", "x86_64-unknown-linux-gnu"] +components = ["rustc", "cargo", "rustfmt", "clippy"] diff --git a/scraper/src/io.rs b/scraper/src/io.rs new file mode 100644 index 00000000..b6b3cba2 --- /dev/null +++ b/scraper/src/io.rs @@ -0,0 +1,36 @@ +use crate::types::*; +use anyhow::ensure; +use sha2::{Digest, Sha256}; +use tokio::fs; +use tokio::io::AsyncWriteExt; + +pub async fn write_to(file: &mut fs::File, content: Entry) -> anyhow::Result<()> { + let s = serde_json::to_string(&content)?; + file.write_all(s.as_bytes()).await?; + Ok(()) +} + +use crate::CACHE_DIR; + +pub async fn request(url: &str) -> anyhow::Result { + println!("[request] sending request to {}", url); + + let hash = Sha256::digest(url.as_bytes()); + let path = format!("{CACHE_DIR}/{:x}", hash); + if let Ok(bytes) = fs::read(&path).await { + if let Ok(text) = String::from_utf8(bytes) { + return Ok(text); + } + } + let res = reqwest::get(url).await?; + let status = res.status().as_u16(); + ensure!( + (200..=299).contains(&status), + "Status check failed: expected 200~299, got {}", + status + ); + let text = res.text().await?; + let mut f = tokio::fs::File::create(path).await?; + f.write_all(text.as_bytes()).await?; + Ok(text) +} diff --git a/scraper/src/logger.rs b/scraper/src/logger.rs new file mode 100644 index 00000000..c2311d87 --- /dev/null +++ b/scraper/src/logger.rs @@ -0,0 +1,43 @@ +pub struct Logger { + limit: usize, + current: usize, + start_ms: i64, + start_sec: i64, + start_min: i64, +} + +impl Logger { + pub fn new(limit: usize) -> Self { + let start_ms = chrono::Local::now().timestamp_millis(); + let start_sec = chrono::Local::now().timestamp(); + let start_min = chrono::Local::now().timestamp() / 60; + Logger { + current: 0, + limit, + start_ms, + start_sec, + start_min, + } + } + pub fn done(&mut self, name: &str) { + let now_ms = chrono::Local::now().timestamp_millis(); + let now_sec = chrono::Local::now().timestamp(); + let now_min = now_sec / 60; + self.current += 1; + let count = self.current; + println!( + "[log] faculty {name} done. ({count} / {}) timestamp: {}ms / {}sec / {}min", + self.limit, + now_ms - self.start_ms, + now_sec - self.start_sec, + now_min - self.start_min, + ); + } + pub fn close(self) -> Result<(), ()> { + if self.limit == self.current { + Ok(()) + } else { + Err(()) + } + } +} diff --git a/scraper/src/main.rs b/scraper/src/main.rs new file mode 100644 index 00000000..d3fd67b6 --- /dev/null +++ b/scraper/src/main.rs @@ -0,0 +1,99 @@ +mod io; +mod logger; +mod parser; +mod types; +mod urls; + +use lazy_static::lazy_static; +use std::time::Duration; +use tokio::{fs, io::AsyncWriteExt}; + +use anyhow::Context; +use tokio::time::sleep; +use types::*; + +use scraper::{Html, Selector}; +use urls::URLS; + +const RESULT_FILE: &str = "./data.json"; +const CACHE_DIR: &str = "./.cache"; + +#[tokio::main(flavor = "multi_thread")] +async fn main() { + println!("[log] starting..."); + + let _ = fs::DirBuilder::new().create(CACHE_DIR).await; + + let mut file = fs::File::create(RESULT_FILE) + .await + .expect("Failed to create file"); + file.write_all("[".as_bytes()).await.unwrap(); + + let total = URLS.len(); + let mut logger = logger::Logger::new(total); + for (faculty_name, base_url) in URLS { + let courses = get_courses_of(base_url).await; + logger.done(faculty_name); + let result = Entry { + name: faculty_name.to_owned(), + courses, + }; + io::write_to(&mut file, result).await.unwrap(); + file.write_all(",".as_bytes()).await.unwrap(); + } + + file.write_all("]".as_bytes()).await.unwrap(); + logger.close().unwrap(); +} + +async fn get_courses_of(base_url: &str) -> Vec { + let courses = page_index_pages(base_url) + .await + .into_iter() + .map(|content_page_url| async { + let html = scrape(&content_page_url).await; + parser::parse_course_info(html) + .context(content_page_url) + .unwrap() + }); + futures::future::join_all(courses) + .await + .into_iter() + .collect:: >() +} + +lazy_static! { + static ref DETAIL_BUTTONS: Selector = + Selector::parse(".catalog-search-result-card-header-detail-link") + .expect("invalid selector"); +} +const BASE_URL: &str = "https://catalog.he.u-tokyo.ac.jp/"; + +async fn page_index_pages(base_url: &str) -> Vec { + let mut urls: Vec = Vec::new(); + for key in 0.. { + let html = scrape(&format!("{}{}", base_url, key)).await; + if html.select(&DETAIL_BUTTONS).next().is_none() { + break; + } + urls.extend( + html.select(&DETAIL_BUTTONS) + .map(|elem| BASE_URL.to_owned() + elem.attr("href").unwrap()), + ); + } + urls +} + +async fn scrape(url: &str) -> Html { + for tries in 0..10 { + let res = io::request(url).await; + match res { + Ok(val) => return Html::parse_document(&val), + Err(err) => { + eprintln!("request error: {err} for {} times", tries + 1); + sleep(Duration::from_millis(200)).await; + } + } + } + panic!("Request failed too many times"); +} diff --git a/scraper/src/parser.rs b/scraper/src/parser.rs new file mode 100644 index 00000000..f3214ed2 --- /dev/null +++ b/scraper/src/parser.rs @@ -0,0 +1,57 @@ +use anyhow::anyhow; +use lazy_static::lazy_static; +use scraper::{Html, Selector}; + +use crate::types::*; + +lazy_static! { + static ref NAME_SELECTOR: Selector = + Selector::parse(".catalog-page-detail-table-cell.name-cell").unwrap(); + static ref TEACHER_SELECTOR: Selector = + Selector::parse(".catalog-page-detail-table-cell.lecturer-cell").unwrap(); + static ref SEMESTER_SELECTOR: Selector = + Selector::parse(".catalog-page-detail-table-cell.semester-cell").unwrap(); + static ref PERIOD_SELECTOR: Selector = + Selector::parse(".catalog-page-detail-table-cell.period-cell").unwrap(); + static ref CODE_SELECTOR: Selector = + Selector::parse(".catalog-page-detail-table-cell.code-cell").unwrap(); +} + +pub fn parse_course_info(html: Html) -> anyhow::Result { + Ok(Course { + name: select(&html, &NAME_SELECTOR, 1)?, + teacher: select(&html, &TEACHER_SELECTOR, 1)?, + semester: select_all(&html, &SEMESTER_SELECTOR, 1)?.join(","), + period: select(&html, &PERIOD_SELECTOR, 1)?, + code: select_all(&html, &CODE_SELECTOR, 1)?.join(" "), + }) +} + +fn select(html: &Html, selector: &Selector, nth: usize) -> anyhow::Result { + html.select(selector) + .nth(nth) + .ok_or(anyhow!( + "Couldn't find matching element for selector {:?}", + selector, + )) + .map(|val| val.text().next().unwrap().trim().to_owned()) +} + +fn select_all<'a>( + html: &'a Html, + selector: &'static Selector, + nth: usize, +) -> anyhow::Result > { + html.select(selector) + .nth(nth) + .ok_or(anyhow!( + "Couldn't find matching element for selector {:?}", + selector, + )) + .map(|val| { + val.text() + .map(|text| text.trim()) + .filter(|&text| !text.is_empty()) + .collect:: >() + }) +} diff --git a/scraper/src/types.rs b/scraper/src/types.rs new file mode 100644 index 00000000..664d3c41 --- /dev/null +++ b/scraper/src/types.rs @@ -0,0 +1,15 @@ +use serde::Serialize; + +#[derive(Serialize, Clone)] +pub struct Course { + pub name: String, + pub teacher: String, + pub semester: String, + pub period: String, + pub code: String, +} +#[derive(Serialize, Clone)] +pub struct Entry { + pub name: String, + pub courses: Vec , +} diff --git a/scraper/src/urls.rs b/scraper/src/urls.rs new file mode 100644 index 00000000..aaa7f78c --- /dev/null +++ b/scraper/src/urls.rs @@ -0,0 +1,42 @@ +pub static URLS: [(&str, &str); 10] = [ + ( + "law", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=1&page=", + ), + ( + "medicine", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=2&page=", + ), + ( + "engineering", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=3&page=", + ), + ( + "arts", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=4&page=", + ), + ( + "science", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=5&page=", + ), + ( + "agriculture", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=6&page=", + ), + ( + "economics", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=7&page=", + ), + ( + "liberal_arts", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=8&page=", + ), + ( + "education", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=9&page=", + ), + ( + "pharmacy", + "https://catalog.he.u-tokyo.ac.jp/result?type=ug&faculty_id=10&page=", + ), +]; diff --git a/web/bun.lockb b/web/bun.lockb index 870ae427d6ecd0b62c08df27cfd69eff83abf329..299f713b26f45b25dbfcf373fc6e6287a0cd91ca 100755 GIT binary patch delta 25 hcmbQYh<)B7_J%Et{08idai)5PdWPG@3>Y~U0sv@H2Lk{A delta 25 dcmbQYh<)B7_J%Et{08hy3}CQb%z%+&Apl!r1+xGE From a3a8e5cea36978a43e6e0611e3506f05cb1e0a63 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Sat, 2 Nov 2024 10:40:34 +0900 Subject: [PATCH 013/131] =?UTF-8?q?=E8=88=88=E5=91=B3=E3=81=AE=E3=81=82?= =?UTF-8?q?=E3=82=8B=E5=88=86=E9=87=8E=20(server=20=E3=81=AE=E3=81=BF)=20(?= =?UTF-8?q?#488)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 ## 具体的な変更内容 ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [x] 改めてセルフレビューしたか - [x] 手動での動作検証を行ったか - [x] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [x] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [x] わかりやすいPRになっているか --- common/types.ts | 1 + common/zod/schemas.ts | 6 + common/zod/types.ts | 2 + server/package.json | 2 +- server/prisma/schema.prisma | 24 ++- server/src/database/interest.test.ts | 25 +++ server/src/database/interest.ts | 39 ++++ server/src/functions/user.test.ts | 3 +- server/src/seeds/data/subjects.ts | 10 ++ server/src/seeds/seed-test.ts | 95 ++++++++++ server/src/seeds/seed.ts | 257 ++------------------------- server/src/seeds/test-data/data.ts | 161 +++++++++++++++++ 12 files changed, 380 insertions(+), 245 deletions(-) create mode 100644 server/src/database/interest.test.ts create mode 100644 server/src/database/interest.ts create mode 100644 server/src/seeds/data/subjects.ts create mode 100644 server/src/seeds/seed-test.ts create mode 100644 server/src/seeds/test-data/data.ts diff --git a/common/types.ts b/common/types.ts index 0bd9ed92..c89a5b5e 100644 --- a/common/types.ts +++ b/common/types.ts @@ -6,6 +6,7 @@ export type { IDToken, Gender, RelationshipStatus, + InterestSubject, User, InitUser, UpdateUser, diff --git a/common/zod/schemas.ts b/common/zod/schemas.ts index 2e8d8c0b..2c1c9cad 100644 --- a/common/zod/schemas.ts +++ b/common/zod/schemas.ts @@ -32,6 +32,12 @@ export const IntroLongSchema = z // .min(2, { message: "自己紹介文は2文字以上です" }) .max(225, { message: "自己紹介文は225文字以下です" }); +export const InterestSubjectSchema = z.object({ + id: z.number(), + name: z.string(), + group: z.string(), +}); + export const UserSchema = z.object({ id: UserIDSchema, guid: GUIDSchema, diff --git a/common/zod/types.ts b/common/zod/types.ts index 6929b022..c0f1a44b 100644 --- a/common/zod/types.ts +++ b/common/zod/types.ts @@ -14,6 +14,7 @@ import type { InitRoomSchema, InitSharedRoomSchema, InitUserSchema, + InterestSubjectSchema, IntroLongSchema, IntroShortSchema, MessageIDSchema, @@ -45,6 +46,7 @@ export type Name = z.infer ; export type PictureUrl = z.infer ; export type Gender = z.infer ; export type RelationshipStatus = z.infer ; +export type InterestSubject = z.infer ; export type User = z.infer ; export type InitUser = z.infer ; export type UpdateUser = z.infer ; diff --git a/server/package.json b/server/package.json index a1974ba3..188e2cca 100644 --- a/server/package.json +++ b/server/package.json @@ -11,7 +11,7 @@ "prisma-generate-sql": "bunx dotenv -e .env.dev -- prisma generate --sql" }, "prisma": { - "seed": "bun src/seeds/seed.ts" + "seed": "bun src/seeds/seed-test.ts" }, "keywords": [], "author": "", diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index c4cdaefd..67986b44 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -1,7 +1,7 @@ //schema.prisma generator client { - provider = "prisma-client-js" - binaryTargets = ["native", "debian-openssl-3.0.x"] + provider = "prisma-client-js" + binaryTargets = ["native", "debian-openssl-3.0.x"] previewFeatures = ["typedSql"] } @@ -39,6 +39,7 @@ model User { enrollments Enrollment[] sendingUsers Relationship[] @relation("sending") // 自分がマッチリクエストを送ったユーザー receivingUsers Relationship[] @relation("receiving") // 自分にマッチリクエストを送ったユーザー + interests Interest[] } // プロフィールの画像。 @@ -47,6 +48,25 @@ model Avatar { data Bytes } +model InterestSubject { + id Int @id @default(autoincrement()) + name String + group String // such as Computer Science | name = ML + + @@unique([name, group]) + Interest Interest[] // ignore this +} + +// User->Interest->InterestSubject +model Interest { + userId Int + user User @relation(fields: [userId], references: [id]) + subjectId Int + subject InterestSubject @relation(fields: [subjectId], references: [id]) + + @@unique([userId, subjectId]) +} + // enum Gender { // MALE // FEMALE diff --git a/server/src/database/interest.test.ts b/server/src/database/interest.test.ts new file mode 100644 index 00000000..b9a22b25 --- /dev/null +++ b/server/src/database/interest.test.ts @@ -0,0 +1,25 @@ +import { beforeAll, expect, test } from "bun:test"; +import { assertLocalDB } from "../load-env"; +import * as interest from "./interest"; + +beforeAll(assertLocalDB); + +test("list", async () => { + const got = (await interest.all()).sort((a, b) => a.id - b.id); + expect(got).toEqual([ + { id: 1, group: "Computer Science", name: "型システム" }, + { id: 2, group: "Computer Science", name: "機械学習" }, + { id: 3, group: "Computer Science", name: "CPU アーキテクチャ" }, + { id: 4, group: "Computer Science", name: "分散処理" }, + { id: 5, group: "Math", name: "Lean4" }, + ]); +}); + +test("get by user id", async () => { + const got = (await interest.of(101)).sort((a, b) => a.id - b.id); + expect(got).toEqual([ + { id: 1, group: "Computer Science", name: "型システム" }, + { id: 2, group: "Computer Science", name: "機械学習" }, + { id: 3, group: "Computer Science", name: "CPU アーキテクチャ" }, + ]); +}); diff --git a/server/src/database/interest.ts b/server/src/database/interest.ts new file mode 100644 index 00000000..00f4b10c --- /dev/null +++ b/server/src/database/interest.ts @@ -0,0 +1,39 @@ +import type { InterestSubject, UserID } from "../common/types"; +import { prisma } from "./client"; + +export async function all(): Promise { + return await prisma.interestSubject.findMany(); +} + +export async function get(id: number): Promise { + return await prisma.interestSubject.findUnique({ where: { id } }); +} + +export async function of(userId: UserID): Promise { + return await prisma.interest + .findMany({ + where: { + userId, + }, + select: { + subject: true, + }, + }) + .then((res) => res.map((interest) => interest.subject)); +} + +export async function add(userId: UserID, subjectId: number) { + return await prisma.interest.create({ + data: { + userId, + subjectId, + }, + }); +} +export async function remove(userId: UserID, subjectId: number) { + return await prisma.interest.delete({ + where: { + userId_subjectId: { userId, subjectId }, + }, + }); +} diff --git a/server/src/functions/user.test.ts b/server/src/functions/user.test.ts index 36c316f4..ba04f302 100644 --- a/server/src/functions/user.test.ts +++ b/server/src/functions/user.test.ts @@ -16,7 +16,8 @@ test("get all users", async () => { expect(result.code).toBe(200); expect(result.body).toSatisfy((s) => s.length === 3); expect(result.body).toSatisfy( - (s) => typeof s !== "string" && s[0].name === "田中太郎", + (s) => + typeof s !== "string" && s.some((person) => person.name === "田中太郎"), ); }); diff --git a/server/src/seeds/data/subjects.ts b/server/src/seeds/data/subjects.ts new file mode 100644 index 00000000..2cfe78ab --- /dev/null +++ b/server/src/seeds/data/subjects.ts @@ -0,0 +1,10 @@ +export const subjects = [ + { + group: "Computer Science", + subjects: ["機械学習", "CPU アーキテクチャ", "型システム", "分散処理"], + }, + { + group: "Math", + subjects: ["Lean4"], + }, +]; diff --git a/server/src/seeds/seed-test.ts b/server/src/seeds/seed-test.ts new file mode 100644 index 00000000..23b54a51 --- /dev/null +++ b/server/src/seeds/seed-test.ts @@ -0,0 +1,95 @@ +import { prisma } from "../database/client"; +import { + courses, + enrollments, + interest, + slots, + subjects, + users, +} from "./test-data/data"; + +async function main() { + await Promise.all( + subjects.map(async ({ group, subjects }) => { + for (const [id, name] of subjects) { + await prisma.interestSubject.upsert({ + where: { id }, + update: { name, group }, + create: { id, name, group }, + }); + } + }), + ); + + await Promise.all( + users.map(async (user) => { + await prisma.user.upsert({ + where: { id: user.id }, + update: {}, + create: user, + }); + }), + ); + + await Promise.all( + interest.map(async (interest) => { + await prisma.interest.upsert({ + where: { + userId_subjectId: interest, + }, + update: interest, + create: interest, + }); + }), + ); + + await Promise.all( + courses.map(async (course) => { + await prisma.course.upsert({ + where: { id: course.id }, + update: course, + create: course, + }); + }), + ); + + await Promise.all( + slots.map(async (slot) => { + await prisma.slot.upsert({ + where: { + courseId_period_day: { + courseId: slot.courseId, + period: slot.period, + day: slot.day, + }, + }, + update: slot, + create: slot, + }); + }), + ); + + const promises = enrollments.map(async ([user, course]) => { + await prisma.enrollment.upsert({ + where: { + userId_courseId: { userId: user, courseId: course }, + }, + update: {}, + create: { + userId: user, + courseId: course, + }, + }); + }); + await Promise.all(promises); +} + +await main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); diff --git a/server/src/seeds/seed.ts b/server/src/seeds/seed.ts index 2cb2fe30..bf98d1a6 100644 --- a/server/src/seeds/seed.ts +++ b/server/src/seeds/seed.ts @@ -1,243 +1,18 @@ import { prisma } from "../database/client"; - -async function main() { - // users - await prisma.user.upsert({ - where: { id: 101 }, - update: {}, - create: { - id: 101, - name: "田中太郎", - gender: "男", - grade: "D2", - faculty: "工学部", - department: "電気電子工学科", - intro: "田中太郎です。", - pictureUrl: - "https://firebasestorage.googleapis.com/v0/b/coursemate-tutorial.appspot.com/o/YdulS1s41LVh1nWgOBqzMiXN7803%2FtP5PrelZVe6v4UoF.jpg?alt=media&token=252da169-cccb-45b3-bec6-946ec3de3e27", - guid: "abc101", - }, - }); - - await prisma.user.upsert({ - where: { id: 102 }, - update: {}, - create: { - id: 102, - name: "山田花子", - gender: "女", - grade: "B2", - faculty: "経済学部", - department: "経営学科", - intro: "山田花子です。", - pictureUrl: - "https://firebasestorage.googleapis.com/v0/b/coursemate-tutorial.appspot.com/o/45QiYkH65OWHZYPruT9sHKAHa4I3%2FulavVaTxMNACkcn4.jpg?alt=media&token=6eea4c9f-c9ec-4c6e-943b-96b0afe013c3", - guid: "abc102", - }, - }); - await prisma.user.upsert({ - where: { id: 103 }, - update: {}, - create: { - id: 103, - name: "小五郎", - gender: "男", - grade: "B3", - faculty: "経済学部", - department: "経営学科", - intro: "小五郎です。", - pictureUrl: - "https://firebasestorage.googleapis.com/v0/b/coursemate-tutorial.appspot.com/o/45QiYkH65OWHZYPruT9sHKAHa4I3%2FulavVaTxMNACkcn4.jpg?alt=media&token=6eea4c9f-c9ec-4c6e-943b-96b0afe013c3", - guid: "abc103", - }, - }); // courses - - await prisma.course.upsert({ - where: { id: "10001" }, - update: {}, - create: { - id: "10001", - name: "国語八列", - teacher: "足助太郎", - }, - }); - await prisma.course.upsert({ - where: { id: "10002" }, - update: {}, - create: { - id: "10002", - name: "数学八列", - teacher: "足助太郎", - }, - }); - await prisma.course.upsert({ - where: { id: "10003" }, - update: {}, - create: { - id: "10003", - name: "英語八列", - teacher: "足助太郎", - }, - }); - await prisma.course.upsert({ - where: { id: "10004" }, - update: {}, - create: { - id: "10004", - name: "理科八列", - teacher: "足助太郎", - }, - }); - await prisma.course.upsert({ - where: { id: "10005" }, - update: {}, - create: { - id: "10005", - name: "社会八列", - teacher: "足助太郎", - }, - }); - - // slot - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10001", period: 4, day: "tue" }, - }, - update: {}, - create: { - courseId: "10001", - day: "tue", - period: 4, - }, - }); - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10001", period: 4, day: "thu" }, - }, - update: {}, - create: { - courseId: "10001", - day: "thu", - period: 4, - }, - }); - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10002", period: 3, day: "mon" }, - }, - update: {}, - create: { - courseId: "10002", - day: "mon", - period: 3, - }, - }); - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10003", period: 3, day: "mon" }, - }, - update: {}, - create: { - courseId: "10003", - day: "mon", - period: 3, - }, - }); - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10003", period: 3, day: "wed" }, - }, - update: {}, - create: { - courseId: "10003", - day: "wed", - period: 3, - }, - }); - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10004", period: 3, day: "wed" }, - }, - update: {}, - create: { - courseId: "10004", - day: "wed", - period: 3, - }, - }); - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10004", period: 3, day: "fri" }, - }, - update: {}, - create: { - courseId: "10004", - day: "fri", - period: 3, - }, - }); - - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10005", period: 2, day: "tue" }, - }, - update: {}, - create: { - courseId: "10005", - day: "tue", - period: 2, - }, - }); - - await prisma.slot.upsert({ - where: { - courseId_period_day: { courseId: "10005", period: 3, day: "tue" }, - }, - update: {}, - create: { - courseId: "10005", - day: "tue", - period: 3, - }, - }); - - // userId, courseId - const enrollments: Array<[number, string]> = [ - // assert: 101 and 102 has more overlaps in courses than 101 and 103, but less than 102 and 103 - // if you change the assertion above, fix test in engines/recommendation.test.ts too. - [101, "10001"], - [101, "10002"], - [101, "10003"], - [102, "10002"], - [102, "10003"], - [102, "10004"], - [102, "10005"], - [103, "10003"], - [103, "10004"], - [103, "10005"], - ]; - - const promises = enrollments.map(async ([user, course]) => { - await prisma.enrollment.upsert({ - where: { - userId_courseId: { userId: user, courseId: course }, - }, - update: {}, - create: { - userId: user, - courseId: course, - }, - }); - }); - await Promise.all(promises); +import { subjects } from "./data/subjects"; + +const promises: Array > = []; +for (const subjectGroup of subjects) { + const group = subjectGroup.group; + for (const name of subjectGroup.subjects) { + promises.push( + prisma.interestSubject.upsert({ + where: { + name_group: { name, group }, + }, + update: { name, group }, + create: { name, group }, + }), + ); + } } - -await main() - .then(async () => { - await prisma.$disconnect(); - }) - .catch(async (e) => { - console.error(e); - await prisma.$disconnect(); - process.exit(1); - }); diff --git a/server/src/seeds/test-data/data.ts b/server/src/seeds/test-data/data.ts new file mode 100644 index 00000000..c4a2714c --- /dev/null +++ b/server/src/seeds/test-data/data.ts @@ -0,0 +1,161 @@ +import type { Day } from "../../common/types"; + +export const subjects: Array<{ + group: string; + subjects: Array<[number, string]>; +}> = [ + { + group: "Computer Science", + subjects: [ + [1, "型システム"], + [2, "機械学習"], + [3, "CPU アーキテクチャ"], + [4, "分散処理"], + ] as const, + }, + { + group: "Math", + subjects: [[5, "Lean4"]], + }, +]; +export const interest = [ + { userId: 101, subjectId: 1 }, + { userId: 102, subjectId: 1 }, + { userId: 103, subjectId: 1 }, + { userId: 101, subjectId: 2 }, + { userId: 101, subjectId: 3 }, + { userId: 102, subjectId: 2 }, + { userId: 102, subjectId: 4 }, + { userId: 103, subjectId: 3 }, + { userId: 103, subjectId: 4 }, +]; + +export const users = [ + { + id: 101, + name: "田中太郎", + gender: "男", + grade: "D2", + faculty: "工学部", + department: "電気電子工学科", + intro: "田中太郎です。", + pictureUrl: + "https://firebasestorage.googleapis.com/v0/b/coursemate-tutorial.appspot.com/o/YdulS1s41LVh1nWgOBqzMiXN7803%2FtP5PrelZVe6v4UoF.jpg?alt=media&token=252da169-cccb-45b3-bec6-946ec3de3e27", + guid: "abc101", + }, + { + id: 102, + name: "山田花子", + gender: "女", + grade: "B2", + faculty: "経済学部", + department: "経営学科", + intro: "山田花子です。", + pictureUrl: + "https://firebasestorage.googleapis.com/v0/b/coursemate-tutorial.appspot.com/o/45QiYkH65OWHZYPruT9sHKAHa4I3%2FulavVaTxMNACkcn4.jpg?alt=media&token=6eea4c9f-c9ec-4c6e-943b-96b0afe013c3", + guid: "abc102", + }, + { + id: 103, + name: "小五郎", + gender: "男", + grade: "B3", + faculty: "経済学部", + department: "経営学科", + intro: "小五郎です。", + pictureUrl: + "https://firebasestorage.googleapis.com/v0/b/coursemate-tutorial.appspot.com/o/45QiYkH65OWHZYPruT9sHKAHa4I3%2FulavVaTxMNACkcn4.jpg?alt=media&token=6eea4c9f-c9ec-4c6e-943b-96b0afe013c3", + guid: "abc103", + }, +]; + +export const courses = [ + { + id: "10001", + name: "国語八列", + teacher: "足助太郎", + }, + { + id: "10002", + name: "数学八列", + teacher: "足助太郎", + }, + { + id: "10003", + name: "英語八列", + teacher: "足助太郎", + }, + { + id: "10004", + name: "理科八列", + teacher: "足助太郎", + }, + { + id: "10005", + name: "社会八列", + teacher: "足助太郎", + }, +]; + +export const slots: Array<{ courseId: string; day: Day; period: number }> = [ + { + courseId: "10001", + day: "tue", + period: 4, + }, + { + courseId: "10001", + day: "thu", + period: 4, + }, + { + courseId: "10002", + day: "mon", + period: 3, + }, + { + courseId: "10003", + day: "mon", + period: 3, + }, + { + courseId: "10003", + day: "wed", + period: 3, + }, + { + courseId: "10004", + day: "wed", + period: 3, + }, + { + courseId: "10004", + day: "fri", + period: 3, + }, + { + courseId: "10005", + day: "tue", + period: 2, + }, + { + courseId: "10005", + day: "tue", + period: 3, + }, +]; + +export const enrollments: Array<[number, string]> = [ + // assert: 101 and 102 has more overlaps in courses than 101 and 103, but less than 102 and 103 + // if you change the assertion above, fix test in engines/recommendation.test.ts too. + [101, "10001"], + [101, "10002"], + [101, "10003"], + [102, "10002"], + [102, "10003"], + [102, "10004"], + [102, "10005"], + [103, "10003"], + [103, "10004"], + [103, "10005"], +]; From 3c8d155da548b3d11b5e9e59beee12e822c51e75 Mon Sep 17 00:00:00 2001 From: Yuki Kobayashi <137767097+aster-void@users.noreply.github.com> Date: Sat, 2 Nov 2024 14:23:27 +0900 Subject: [PATCH 014/131] =?UTF-8?q?=E4=BB=BB=E6=84=8F=E3=81=AE=E3=83=A1?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=82=A2=E3=83=89=E3=83=AC=E3=82=B9=E3=81=A7?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=82=A4=E3=83=B3=E3=82=92=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B=20(#509)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PRの概要 ## 具体的な変更内容 ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [x] 改めてセルフレビューしたか - [x] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [x] わかりやすいPRになっているか --- README.md | 15 +++++++++++++++ web/.env.sample | 1 + web/src/routes/login.tsx | 14 ++++++++++---- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cfcff829..d34d4f9c 100644 --- a/README.md +++ b/README.md @@ -59,3 +59,18 @@ make docker # または make docker-watch ``` + +## Deploy + +web: +```sh +VITE_ALLOW_ANY_MAIL_ADDR=true # optional +make prepare-deploy-web` +# serve ./web/dist +``` + +server: +```sh +make prepare-deploy-server +make deploy-server +``` diff --git a/web/.env.sample b/web/.env.sample index 05df56ba..87ad24dd 100644 --- a/web/.env.sample +++ b/web/.env.sample @@ -6,6 +6,7 @@ VITE_FIREBASE_PROJECT_ID=example # VITE_FIREBASE_MESSAGING_SENDER_ID=example VITE_FIREBASE_APP_ID=example VITE_FIREBASE_MEASUREMENT_ID=example +VITE_ALLOW_ANY_MAIL_ADDR=true #supabase VITE_SUPABASE_URL= diff --git a/web/src/routes/login.tsx b/web/src/routes/login.tsx index 8a3eec2e..23843c2a 100644 --- a/web/src/routes/login.tsx +++ b/web/src/routes/login.tsx @@ -13,12 +13,15 @@ import { CourseMateIcon } from "../components/common/CourseMateIcon"; import FullScreenCircularProgress from "../components/common/FullScreenCircularProgress"; const provider = new GoogleAuthProvider(); +const ALLOW_ANY_MAIL_ADDR = import.meta.env.VITE_ALLOW_ANY_MAIL_ADDR === "true"; async function signInWithGoogle() { try { - provider.setCustomParameters({ - hd: "g.ecc.u-tokyo.ac.jp", - }); + if (!ALLOW_ANY_MAIL_ADDR) { + provider.setCustomParameters({ + hd: "g.ecc.u-tokyo.ac.jp", + }); + } const result = await signInWithPopup(auth, provider); const credential = GoogleAuthProvider.credentialFromResult(result); @@ -29,7 +32,10 @@ async function signInWithGoogle() { const user = result.user; const email = user.email; - if (!email || !email.endsWith("@g.ecc.u-tokyo.ac.jp")) { + if ( + !ALLOW_ANY_MAIL_ADDR && + (!email || !email.endsWith("@g.ecc.u-tokyo.ac.jp")) + ) { throw new Error( "Unauthorized domain. Access is restricted to g.ecc.u-tokyo.ac.jp domain.", ); From 5737ddd93e238eef81dedfaee3da248a872cdbb1 Mon Sep 17 00:00:00 2001 From: Shogo NAKAMURA <104970808+naka-12@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:53:45 +0900 Subject: [PATCH 015/131] =?UTF-8?q?Vite=20+=20React=20Router=20=E2=86=92?= =?UTF-8?q?=20Next.js=20(#512)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vite, React Router から Next.js に移行 (Express はそのまま) --- .cspell.json | 3 +- .github/workflows/ci.yml | 8 + Makefile | 4 +- README.md | 2 +- biome.json | 11 +- bun.lockb | Bin 71909 -> 71964 bytes package-lock.json | 1671 ++++++++++++++++ package.json | 3 +- server/package-lock.json | 1111 +---------- web/.env.sample | 22 +- web/.gitignore | 6 + web/Dockerfile | 2 +- web/{src => }/api/chat/chat.ts | 2 +- web/{src => }/api/chat/hooks.ts | 16 +- web/api/course.hook.ts | 10 + web/{src => }/api/course.ts | 2 +- web/{src => }/api/image.ts | 0 web/{src => }/api/internal/endpoints.ts | 8 +- web/{src => }/api/internal/fetch-func.ts | 2 +- web/{src => }/api/match.ts | 0 web/{src => }/api/request.ts | 0 web/{src => }/api/share/types.ts | 0 web/{src => }/api/user.ts | 22 +- web/app/chat/layout.tsx | 31 + web/app/chat/page.tsx | 41 + web/app/edit/courses/page.tsx | 78 + web/app/edit/layout.tsx | 29 + web/app/edit/profile/page.tsx | 552 ++++++ web/app/error.tsx | 11 + web/{src/routes/faq.tsx => app/faq/page.tsx} | 10 +- web/app/favicon.ico | Bin 0 -> 15086 bytes web/app/friends/layout.tsx | 31 + .../tabs/friends.tsx => app/friends/page.tsx} | 9 +- web/app/home/layout.tsx | 31 + .../tabs/home.tsx => app/home/page.tsx} | 118 +- web/{src => app}/index.css | 0 web/app/layout.tsx | 63 + .../routes/login.tsx => app/login/page.tsx} | 38 +- .../styles/login.css => app/login/style.css} | 0 web/app/not-found.tsx | 10 + web/app/page.tsx | 5 + web/app/settings/aboutUs/page.tsx | 126 ++ web/app/settings/contact/page.tsx | 45 + .../settings/delete/page.tsx} | 14 +- web/app/settings/disclaimer/page.tsx | 59 + web/app/settings/layout.tsx | 31 + web/app/settings/page.tsx | 58 + web/app/settings/profile/page.tsx | 72 + .../registration => app/signup}/common.tsx | 0 .../registration => app/signup}/data.ts | 0 .../registration => app/signup}/functions.ts | 8 +- .../index.tsx => app/signup/page.tsx} | 11 +- .../signup}/steps/step1_profile.tsx | 8 +- .../signup}/steps/step2_img.tsx | 2 +- .../signup}/steps/step3_confirmation.tsx | 4 +- .../signup}/steps/step4_course.tsx | 5 +- .../tutorial.tsx => app/tutorial/page.tsx} | 26 +- web/bun.lockb | Bin 119070 -> 124783 bytes web/{src => }/components/BanLandscape.tsx | 2 + web/components/BottomBar.tsx | 84 + web/{src => }/components/Card.tsx | 0 web/{src => }/components/DraggableCard.tsx | 2 +- web/{src => }/components/Header.tsx | 11 +- web/{src => }/components/ImageCropper.tsx | 0 web/{src => }/components/ImageFallback.tsx | 4 +- web/{src => }/components/LogOutButton.tsx | 10 +- web/{src => }/components/about.tsx | 0 .../components/chat/MessageInput.tsx | 0 web/{src => }/components/chat/Room.tsx | 2 +- web/{src => }/components/chat/RoomHeader.tsx | 8 +- web/{src => }/components/chat/RoomList.tsx | 19 +- web/{src => }/components/chat/RoomWindow.tsx | 17 +- .../components/common/CourseMateIcon.tsx | 0 web/{src => }/components/common/Dots.tsx | 0 .../common/FullScreenCircularProgress.tsx | 0 .../components/common/NavigateByAuthState.tsx | 17 +- web/{src => }/components/common/Popup.tsx | 0 .../common}/TopNavigation.tsx | 9 +- .../components/common/alert/AlertProvider.tsx | 2 + .../components/common/alert/alertContext.ts | 0 .../components/common/modal/ModalProvider.tsx | 0 .../components/config/PhotoModal.tsx | 0 .../components/config/PhotoPreview.tsx | 0 .../course/EditableCoursesTable/index.tsx | 0 .../course/NonEditableCoursesTable/index.tsx | 0 .../components/CourseDeleteConfirmDialog.tsx | 0 .../CourseRegisterConfirmDialog.tsx | 0 .../components/CoursesTableCore/index.tsx | 0 .../course/components/CoursesTableCore/lib.ts | 0 .../CoursesTableCore/styles.module.css | 0 .../course/components/SelectCourseDialog.tsx | 0 .../components/data/photo-preview.tsx | 0 web/{src => }/components/data/socket.ts | 6 +- .../components/human/WithFallback.tsx | 0 web/{src => }/components/human/avatar.tsx | 0 .../components/human/humanListItem.tsx | 0 web/{src => }/components/match/matching.tsx | 0 web/{src => }/components/match/myRequests.tsx | 0 .../components/match/othersRequests.tsx | 0 web/{src => }/components/match/requests.tsx | 0 web/{src => }/firebase/auth/AuthProvider.tsx | 2 +- web/{src => }/firebase/auth/lib.ts | 2 +- web/firebase/config.ts | 17 + web/{src => }/hooks/useCurrentUser.ts | 0 .../useSWR.ts => hooks/useCustomizedSWR.ts} | 18 +- web/{src => }/hooks/useData.ts | 2 +- web/index.html | 13 - web/next.config.mjs | 6 + web/package-lock.json | 1723 +++++++++++------ web/package.json | 14 +- web/src/.gitignore | 1 - web/src/App.tsx | 196 -- web/src/api/course.hook.ts | 10 - web/src/firebase/config.ts | 17 - web/src/main.tsx | 29 - web/src/routes/editCourses.tsx | 81 - web/src/routes/editProfile.tsx | 547 ------ web/src/routes/root.tsx | 133 -- web/src/routes/tabs/chat.tsx | 18 - web/src/routes/tabs/settings/aboutUs.tsx | 123 -- web/src/routes/tabs/settings/contact.tsx | 42 - web/src/routes/tabs/settings/disclaimer.tsx | 56 - web/src/routes/tabs/settings/profile.tsx | 72 - web/src/routes/tabs/settings/settings.tsx | 58 - web/src/vite-env.d.ts | 1 - web/tsconfig.json | 29 +- web/tsconfig.node.json | 11 - web/vite.config.ts | 7 - 128 files changed, 4668 insertions(+), 3184 deletions(-) create mode 100644 package-lock.json rename web/{src => }/api/chat/chat.ts (99%) rename web/{src => }/api/chat/hooks.ts (62%) create mode 100644 web/api/course.hook.ts rename web/{src => }/api/course.ts (97%) rename web/{src => }/api/image.ts (100%) rename web/{src => }/api/internal/endpoints.ts (96%) rename web/{src => }/api/internal/fetch-func.ts (93%) rename web/{src => }/api/match.ts (100%) rename web/{src => }/api/request.ts (100%) rename web/{src => }/api/share/types.ts (100%) rename web/{src => }/api/user.ts (84%) create mode 100644 web/app/chat/layout.tsx create mode 100644 web/app/chat/page.tsx create mode 100644 web/app/edit/courses/page.tsx create mode 100644 web/app/edit/layout.tsx create mode 100644 web/app/edit/profile/page.tsx create mode 100644 web/app/error.tsx rename web/{src/routes/faq.tsx => app/faq/page.tsx} (93%) create mode 100644 web/app/favicon.ico create mode 100644 web/app/friends/layout.tsx rename web/{src/routes/tabs/friends.tsx => app/friends/page.tsx} (87%) create mode 100644 web/app/home/layout.tsx rename web/{src/routes/tabs/home.tsx => app/home/page.tsx} (61%) rename web/{src => app}/index.css (100%) create mode 100644 web/app/layout.tsx rename web/{src/routes/login.tsx => app/login/page.tsx} (88%) rename web/{src/styles/login.css => app/login/style.css} (100%) create mode 100644 web/app/not-found.tsx create mode 100644 web/app/page.tsx create mode 100644 web/app/settings/aboutUs/page.tsx create mode 100644 web/app/settings/contact/page.tsx rename web/{src/routes/tabs/settings/deleteAccount.tsx => app/settings/delete/page.tsx} (90%) create mode 100644 web/app/settings/disclaimer/page.tsx create mode 100644 web/app/settings/layout.tsx create mode 100644 web/app/settings/page.tsx create mode 100644 web/app/settings/profile/page.tsx rename web/{src/routes/registration => app/signup}/common.tsx (100%) rename web/{src/routes/registration => app/signup}/data.ts (100%) rename web/{src/routes/registration => app/signup}/functions.ts (85%) rename web/{src/routes/registration/index.tsx => app/signup/page.tsx} (90%) rename web/{src/routes/registration => app/signup}/steps/step1_profile.tsx (95%) rename web/{src/routes/registration => app/signup}/steps/step2_img.tsx (97%) rename web/{src/routes/registration => app/signup}/steps/step3_confirmation.tsx (95%) rename web/{src/routes/registration => app/signup}/steps/step4_course.tsx (89%) rename web/{src/routes/tutorial.tsx => app/tutorial/page.tsx} (83%) rename web/{src => }/components/BanLandscape.tsx (99%) create mode 100644 web/components/BottomBar.tsx rename web/{src => }/components/Card.tsx (100%) rename web/{src => }/components/DraggableCard.tsx (98%) rename web/{src => }/components/Header.tsx (78%) rename web/{src => }/components/ImageCropper.tsx (100%) rename web/{src => }/components/ImageFallback.tsx (92%) rename web/{src => }/components/LogOutButton.tsx (87%) rename web/{src => }/components/about.tsx (100%) rename web/{src => }/components/chat/MessageInput.tsx (100%) rename web/{src => }/components/chat/Room.tsx (93%) rename web/{src => }/components/chat/RoomHeader.tsx (85%) rename web/{src => }/components/chat/RoomList.tsx (75%) rename web/{src => }/components/chat/RoomWindow.tsx (94%) rename web/{src => }/components/common/CourseMateIcon.tsx (100%) rename web/{src => }/components/common/Dots.tsx (100%) rename web/{src => }/components/common/FullScreenCircularProgress.tsx (100%) rename web/{src => }/components/common/NavigateByAuthState.tsx (82%) rename web/{src => }/components/common/Popup.tsx (100%) rename web/{src/routes/tabs/settings/components => components/common}/TopNavigation.tsx (86%) rename web/{src => }/components/common/alert/AlertProvider.tsx (99%) rename web/{src => }/components/common/alert/alertContext.ts (100%) rename web/{src => }/components/common/modal/ModalProvider.tsx (100%) rename web/{src => }/components/config/PhotoModal.tsx (100%) rename web/{src => }/components/config/PhotoPreview.tsx (100%) rename web/{src => }/components/course/EditableCoursesTable/index.tsx (100%) rename web/{src => }/components/course/NonEditableCoursesTable/index.tsx (100%) rename web/{src => }/components/course/components/CourseDeleteConfirmDialog.tsx (100%) rename web/{src => }/components/course/components/CourseRegisterConfirmDialog.tsx (100%) rename web/{src => }/components/course/components/CoursesTableCore/index.tsx (100%) rename web/{src => }/components/course/components/CoursesTableCore/lib.ts (100%) rename web/{src => }/components/course/components/CoursesTableCore/styles.module.css (100%) rename web/{src => }/components/course/components/SelectCourseDialog.tsx (100%) rename web/{src => }/components/data/photo-preview.tsx (100%) rename web/{src => }/components/data/socket.ts (51%) rename web/{src => }/components/human/WithFallback.tsx (100%) rename web/{src => }/components/human/avatar.tsx (100%) rename web/{src => }/components/human/humanListItem.tsx (100%) rename web/{src => }/components/match/matching.tsx (100%) rename web/{src => }/components/match/myRequests.tsx (100%) rename web/{src => }/components/match/othersRequests.tsx (100%) rename web/{src => }/components/match/requests.tsx (100%) rename web/{src => }/firebase/auth/AuthProvider.tsx (94%) rename web/{src => }/firebase/auth/lib.ts (96%) create mode 100644 web/firebase/config.ts rename web/{src => }/hooks/useCurrentUser.ts (100%) rename web/{src/hooks/useSWR.ts => hooks/useCustomizedSWR.ts} (78%) rename web/{src => }/hooks/useData.ts (96%) delete mode 100644 web/index.html create mode 100644 web/next.config.mjs delete mode 100644 web/src/.gitignore delete mode 100644 web/src/App.tsx delete mode 100644 web/src/api/course.hook.ts delete mode 100644 web/src/firebase/config.ts delete mode 100644 web/src/main.tsx delete mode 100644 web/src/routes/editCourses.tsx delete mode 100644 web/src/routes/editProfile.tsx delete mode 100644 web/src/routes/root.tsx delete mode 100644 web/src/routes/tabs/chat.tsx delete mode 100644 web/src/routes/tabs/settings/aboutUs.tsx delete mode 100644 web/src/routes/tabs/settings/contact.tsx delete mode 100644 web/src/routes/tabs/settings/disclaimer.tsx delete mode 100644 web/src/routes/tabs/settings/profile.tsx delete mode 100644 web/src/routes/tabs/settings/settings.tsx delete mode 100644 web/src/vite-env.d.ts delete mode 100644 web/tsconfig.node.json delete mode 100644 web/vite.config.ts diff --git a/.cspell.json b/.cspell.json index 1f059cd5..22705a27 100644 --- a/.cspell.json +++ b/.cspell.json @@ -45,6 +45,7 @@ "**/data.json", "**/Cargo.*", "scraper/target", - "**/rust-toolchain.toml" + "**/rust-toolchain.toml", + "web/out" ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 102c2e26..f40718eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,14 @@ on: env: DATABASE_URL: ${{ secrets.DATABASE_URL_FOR_PRISMA_SQL_GENERATION }} + NEXT_PUBLIC_API_ENDPOINT: "sample" + NEXT_PUBLIC_FIREBASE_API_KEY: "sample" + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: "sample" + NEXT_PUBLIC_FIREBASE_PROJECT_ID: "sample" + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: "sample" + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: "sample" + NEXT_PUBLIC_FIREBASE_APP_ID: "sample" + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: "sample" jobs: build: name: Build diff --git a/Makefile b/Makefile index 72b7c243..d70acbf8 100644 --- a/Makefile +++ b/Makefile @@ -166,7 +166,7 @@ copy-common-to-server: @ if [ -d server/src/common ]; then rm -r server/src/common; fi @ cp -r common server/src/common copy-common-to-web: - @ if [ -d web/src/common ]; then rm -r web/src/common; fi - @ cp -r common web/src/common + @ if [ -d web/common ]; then rm -r web/common; fi + @ cp -r common web/common .PHONY: test diff --git a/README.md b/README.md index d34d4f9c..f9d8a2e7 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ make docker-watch web: ```sh -VITE_ALLOW_ANY_MAIL_ADDR=true # optional +NEXT_PUBLIC_ALLOW_ANY_MAIL_ADDR=true # optional make prepare-deploy-web` # serve ./web/dist ``` diff --git a/biome.json b/biome.json index b2ca8999..6381c36c 100644 --- a/biome.json +++ b/biome.json @@ -10,6 +10,15 @@ "useIgnoreFile": true }, "files": { - "ignore": ["bun.lockb", "server/target", "data.json", "scraper/target"] + "ignore": [ + "bun.lockb", + "server/target", + "data.json", + "scraper/target", + ".next", + "next-env.d.ts", + "out", + "package-lock.json" + ] } } diff --git a/bun.lockb b/bun.lockb index 6a861deffa7dc8296ef865b1e3189000a02a5421..176fd1dd3aad6e186658b20348807dba8f939cc3 100755 GIT binary patch delta 50 zcmaF5k!8*%mI;20J&pcb{TbOLgiC@-gG*dpQaU6U89<;%f^o9`0R@&tb8=Qpp4d47 E0ITT`$N&HU delta 21 dcmbQUiRI}=mI;20p^g4q{TbOLCeQ4g003XV2w4CC diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..f8dd50fc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1671 @@ +{ + "name": "course-mate", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "course-mate", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/bun": "^1.1.10", + "cspell": "^8.14.4", + "zod": "^3.23.8" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.1", + "husky": "^9.1.4", + "lint-staged": "^15.2.10", + "typescript": "^5.6.2" + } + }, + "node_modules/@biomejs/biome": { + "version": "1.9.1", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.1", + "@biomejs/cli-darwin-x64": "1.9.1", + "@biomejs/cli-linux-arm64": "1.9.1", + "@biomejs/cli-linux-arm64-musl": "1.9.1", + "@biomejs/cli-linux-x64": "1.9.1", + "@biomejs/cli-linux-x64-musl": "1.9.1", + "@biomejs/cli-win32-arm64": "1.9.1", + "@biomejs/cli-win32-x64": "1.9.1" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@cspell/cspell-bundled-dicts": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/dict-ada": "^4.0.2", + "@cspell/dict-aws": "^4.0.4", + "@cspell/dict-bash": "^4.1.4", + "@cspell/dict-companies": "^3.1.4", + "@cspell/dict-cpp": "^5.1.16", + "@cspell/dict-cryptocurrencies": "^5.0.0", + "@cspell/dict-csharp": "^4.0.2", + "@cspell/dict-css": "^4.0.13", + "@cspell/dict-dart": "^2.2.1", + "@cspell/dict-django": "^4.1.0", + "@cspell/dict-docker": "^1.1.7", + "@cspell/dict-dotnet": "^5.0.5", + "@cspell/dict-elixir": "^4.0.3", + "@cspell/dict-en_us": "^4.3.23", + "@cspell/dict-en-common-misspellings": "^2.0.4", + "@cspell/dict-en-gb": "1.1.33", + "@cspell/dict-filetypes": "^3.0.4", + "@cspell/dict-flutter": "^1.0.0", + "@cspell/dict-fonts": "^4.0.0", + "@cspell/dict-fsharp": "^1.0.1", + "@cspell/dict-fullstack": "^3.2.0", + "@cspell/dict-gaming-terms": "^1.0.5", + "@cspell/dict-git": "^3.0.0", + "@cspell/dict-golang": "^6.0.12", + "@cspell/dict-google": "^1.0.1", + "@cspell/dict-haskell": "^4.0.1", + "@cspell/dict-html": "^4.0.5", + "@cspell/dict-html-symbol-entities": "^4.0.0", + "@cspell/dict-java": "^5.0.7", + "@cspell/dict-julia": "^1.0.1", + "@cspell/dict-k8s": "^1.0.6", + "@cspell/dict-latex": "^4.0.0", + "@cspell/dict-lorem-ipsum": "^4.0.0", + "@cspell/dict-lua": "^4.0.3", + "@cspell/dict-makefile": "^1.0.0", + "@cspell/dict-monkeyc": "^1.0.6", + "@cspell/dict-node": "^5.0.1", + "@cspell/dict-npm": "^5.1.4", + "@cspell/dict-php": "^4.0.10", + "@cspell/dict-powershell": "^5.0.8", + "@cspell/dict-public-licenses": "^2.0.8", + "@cspell/dict-python": "^4.2.6", + "@cspell/dict-r": "^2.0.1", + "@cspell/dict-ruby": "^5.0.3", + "@cspell/dict-rust": "^4.0.5", + "@cspell/dict-scala": "^5.0.3", + "@cspell/dict-software-terms": "^4.1.3", + "@cspell/dict-sql": "^2.1.5", + "@cspell/dict-svelte": "^1.0.2", + "@cspell/dict-swift": "^2.0.1", + "@cspell/dict-terraform": "^1.0.1", + "@cspell/dict-typescript": "^3.1.6", + "@cspell/dict-vue": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-json-reporter": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.14.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-pipe": { + "version": "8.14.4", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-resolver": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-service-bus": { + "version": "8.14.4", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/cspell-types": { + "version": "8.14.4", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/dict-ada": { + "version": "4.0.2", + "license": "MIT" + }, + "node_modules/@cspell/dict-aws": { + "version": "4.0.4", + "license": "MIT" + }, + "node_modules/@cspell/dict-bash": { + "version": "4.1.4", + "license": "MIT" + }, + "node_modules/@cspell/dict-companies": { + "version": "3.1.4", + "license": "MIT" + }, + "node_modules/@cspell/dict-cpp": { + "version": "5.1.16", + "license": "MIT" + }, + "node_modules/@cspell/dict-cryptocurrencies": { + "version": "5.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-csharp": { + "version": "4.0.2", + "license": "MIT" + }, + "node_modules/@cspell/dict-css": { + "version": "4.0.13", + "license": "MIT" + }, + "node_modules/@cspell/dict-dart": { + "version": "2.2.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-data-science": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-django": { + "version": "4.1.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-docker": { + "version": "1.1.7", + "license": "MIT" + }, + "node_modules/@cspell/dict-dotnet": { + "version": "5.0.5", + "license": "MIT" + }, + "node_modules/@cspell/dict-elixir": { + "version": "4.0.3", + "license": "MIT" + }, + "node_modules/@cspell/dict-en_us": { + "version": "4.3.23", + "license": "MIT" + }, + "node_modules/@cspell/dict-en-common-misspellings": { + "version": "2.0.4", + "license": "CC BY-SA 4.0" + }, + "node_modules/@cspell/dict-en-gb": { + "version": "1.1.33", + "license": "MIT" + }, + "node_modules/@cspell/dict-filetypes": { + "version": "3.0.4", + "license": "MIT" + }, + "node_modules/@cspell/dict-flutter": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-fonts": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-fsharp": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-fullstack": { + "version": "3.2.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-gaming-terms": { + "version": "1.0.5", + "license": "MIT" + }, + "node_modules/@cspell/dict-git": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-golang": { + "version": "6.0.12", + "license": "MIT" + }, + "node_modules/@cspell/dict-google": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-haskell": { + "version": "4.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-html": { + "version": "4.0.6", + "license": "MIT" + }, + "node_modules/@cspell/dict-html-symbol-entities": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-java": { + "version": "5.0.7", + "license": "MIT" + }, + "node_modules/@cspell/dict-julia": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-k8s": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/@cspell/dict-latex": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-lorem-ipsum": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-lua": { + "version": "4.0.3", + "license": "MIT" + }, + "node_modules/@cspell/dict-makefile": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dict-monkeyc": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/@cspell/dict-node": { + "version": "5.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-npm": { + "version": "5.1.5", + "license": "MIT" + }, + "node_modules/@cspell/dict-php": { + "version": "4.0.10", + "license": "MIT" + }, + "node_modules/@cspell/dict-powershell": { + "version": "5.0.9", + "license": "MIT" + }, + "node_modules/@cspell/dict-public-licenses": { + "version": "2.0.8", + "license": "MIT" + }, + "node_modules/@cspell/dict-python": { + "version": "4.2.6", + "license": "MIT", + "dependencies": { + "@cspell/dict-data-science": "^2.0.1" + } + }, + "node_modules/@cspell/dict-r": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-ruby": { + "version": "5.0.3", + "license": "MIT" + }, + "node_modules/@cspell/dict-rust": { + "version": "4.0.5", + "license": "MIT" + }, + "node_modules/@cspell/dict-scala": { + "version": "5.0.3", + "license": "MIT" + }, + "node_modules/@cspell/dict-software-terms": { + "version": "4.1.4", + "license": "MIT" + }, + "node_modules/@cspell/dict-sql": { + "version": "2.1.5", + "license": "MIT" + }, + "node_modules/@cspell/dict-svelte": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/@cspell/dict-swift": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-terraform": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/@cspell/dict-typescript": { + "version": "3.1.6", + "license": "MIT" + }, + "node_modules/@cspell/dict-vue": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/@cspell/dynamic-import": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "import-meta-resolve": "^4.1.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@cspell/filetypes": { + "version": "8.14.4", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/strong-weak-map": { + "version": "8.14.4", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspell/url": { + "version": "8.14.4", + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/bun": { + "version": "1.1.10", + "license": "MIT", + "dependencies": { + "bun-types": "1.1.29" + } + }, + "node_modules/@types/node": { + "version": "20.12.14", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bun-types": { + "version": "1.1.29", + "license": "MIT", + "dependencies": { + "@types/node": "~20.12.8", + "@types/ws": "~8.5.10" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/clear-module": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "12.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/comment-json": { + "version": "4.2.5", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cspell": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-json-reporter": "8.14.4", + "@cspell/cspell-pipe": "8.14.4", + "@cspell/cspell-types": "8.14.4", + "@cspell/dynamic-import": "8.14.4", + "@cspell/url": "8.14.4", + "chalk": "^5.3.0", + "chalk-template": "^1.1.0", + "commander": "^12.1.0", + "cspell-dictionary": "8.14.4", + "cspell-gitignore": "8.14.4", + "cspell-glob": "8.14.4", + "cspell-io": "8.14.4", + "cspell-lib": "8.14.4", + "fast-glob": "^3.3.2", + "fast-json-stable-stringify": "^2.1.0", + "file-entry-cache": "^9.1.0", + "get-stdin": "^9.0.0", + "semver": "^7.6.3", + "strip-ansi": "^7.1.0" + }, + "bin": { + "cspell": "bin.mjs", + "cspell-esm": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" + } + }, + "node_modules/cspell-config-lib": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-types": "8.14.4", + "comment-json": "^4.2.5", + "yaml": "^2.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-dictionary": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.14.4", + "@cspell/cspell-types": "8.14.4", + "cspell-trie-lib": "8.14.4", + "fast-equals": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-gitignore": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/url": "8.14.4", + "cspell-glob": "8.14.4", + "cspell-io": "8.14.4", + "find-up-simple": "^1.0.0" + }, + "bin": { + "cspell-gitignore": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-glob": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/url": "8.14.4", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-grammar": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.14.4", + "@cspell/cspell-types": "8.14.4" + }, + "bin": { + "cspell-grammar": "bin.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-io": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-service-bus": "8.14.4", + "@cspell/url": "8.14.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-lib": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "8.14.4", + "@cspell/cspell-pipe": "8.14.4", + "@cspell/cspell-resolver": "8.14.4", + "@cspell/cspell-types": "8.14.4", + "@cspell/dynamic-import": "8.14.4", + "@cspell/filetypes": "8.14.4", + "@cspell/strong-weak-map": "8.14.4", + "@cspell/url": "8.14.4", + "clear-module": "^4.1.2", + "comment-json": "^4.2.5", + "cspell-config-lib": "8.14.4", + "cspell-dictionary": "8.14.4", + "cspell-glob": "8.14.4", + "cspell-grammar": "8.14.4", + "cspell-io": "8.14.4", + "cspell-trie-lib": "8.14.4", + "env-paths": "^3.0.0", + "fast-equals": "^5.0.1", + "gensequence": "^7.0.0", + "import-fresh": "^3.3.0", + "resolve-from": "^5.0.0", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-uri": "^3.0.8", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/cspell-trie-lib": { + "version": "8.14.4", + "license": "MIT", + "dependencies": { + "@cspell/cspell-pipe": "8.14.4", + "@cspell/cspell-types": "8.14.4", + "gensequence": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-equals": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "9.1.0", + "license": "MIT", + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "license": "ISC" + }, + "node_modules/gensequence": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.4", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/parent-module": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ini": { + "version": "4.1.1", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lint-staged": { + "version": "15.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.8", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "8.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "callsites": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "license": "MIT" + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yaml": { + "version": "2.5.1", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json index d12a6c1e..d8731d54 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,6 @@ }, "lint-staged": { "*.{js,jsx,ts,tsx,json}": ["biome check --write"] - } + }, + "trustedDependencies": ["@biomejs/biome"] } diff --git a/server/package-lock.json b/server/package-lock.json index 704db8b5..049ba3dd 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -10,437 +10,23 @@ "license": "ISC", "dependencies": { "@prisma/client": "^5.20.0", - "@types/cors": "^2.8.17", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.5", - "express": "^4.21.0", - "firebase-admin": "^12.5.0", + "dotenv-cli": "^7.4.2", + "express": "^4.18.2", + "firebase-admin": "^12.2.0", "sharp": "^0.33.5", - "socket.io": "^4.8.0", + "socket.io": "^4.7.5", "zod": "^3.23.8" }, "devDependencies": { "@types/cookie-parser": "^1.4.7", + "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/react": "^18.3.10", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "globals": "^15.9.0", - "prisma": "^5.20.0", - "typescript": "^5.6.2", - "vite": "^5.4.8" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/picocolors": { - "version": "1.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/compat-data": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-module-transforms": "^7.24.8", - "@babel/helpers": "^7.24.8", - "@babel/parser": "^7.24.8", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.8", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/picocolors": { - "version": "1.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/parser": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.24.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.24.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "globals": "^15.8.0", + "prisma": "^5.11.0", + "typescript": "^5.4.5" } }, "node_modules/@fastify/busboy": { @@ -652,49 +238,6 @@ "@img/sharp-libvips-linuxmusl-x64": "1.0.4" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "license": "MIT", @@ -814,30 +357,6 @@ "license": "BSD-3-Clause", "optional": true }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "license": "MIT" @@ -850,43 +369,6 @@ "node": ">= 10" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, "node_modules/@types/body-parser": { "version": "1.19.5", "license": "MIT", @@ -926,11 +408,6 @@ "@types/node": "*" } }, - "node_modules/@types/estree": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, "node_modules/@types/express": { "version": "4.17.21", "license": "MIT", @@ -978,11 +455,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "dev": true, - "license": "MIT" - }, "node_modules/@types/qs": { "version": "6.9.15", "license": "MIT" @@ -991,32 +463,6 @@ "version": "1.2.7", "license": "MIT" }, - "node_modules/@types/react": { - "version": "18.3.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-dom/node_modules/@types/react": { - "version": "18.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, "node_modules/@types/request": { "version": "2.48.12", "license": "MIT", @@ -1050,24 +496,6 @@ "license": "MIT", "optional": true }, - "node_modules/@vitejs/plugin-react": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.5", - "@babel/plugin-transform-react-jsx-self": "^7.24.5", - "@babel/plugin-transform-react-jsx-source": "^7.24.1", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" - } - }, "node_modules/abort-controller": { "version": "3.0.0", "license": "MIT", @@ -1130,30 +558,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-styles/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ansi-styles/node_modules/color-convert/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/array-flatten": { "version": "1.1.1", "license": "MIT" @@ -1235,37 +639,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/browserslist": { - "version": "4.23.2", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "license": "BSD-3-Clause" @@ -1282,48 +655,16 @@ "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001641", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/cliui": { @@ -1400,11 +741,6 @@ "node": ">= 0.6" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, "node_modules/cookie": { "version": "0.4.1", "license": "MIT", @@ -1438,10 +774,18 @@ "node": ">= 0.10" } }, - "node_modules/csstype": { - "version": "3.1.3", - "dev": true, - "license": "MIT" + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, "node_modules/debug": { "version": "2.6.9", @@ -1505,6 +849,28 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.2.tgz", + "integrity": "sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==", + "dependencies": { + "cross-spawn": "^7.0.3", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "engines": { + "node": ">=12" + } + }, "node_modules/duplexify": { "version": "4.1.3", "license": "MIT", @@ -1527,11 +893,6 @@ "version": "1.1.1", "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.4.827", - "dev": true, - "license": "ISC" - }, "node_modules/emoji-regex": { "version": "8.0.0", "license": "MIT", @@ -1632,47 +993,10 @@ "node": ">= 0.4" } }, - "node_modules/esbuild": { - "version": "0.21.5", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, "node_modules/escalade": { "version": "3.1.2", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=6" } @@ -1681,14 +1005,6 @@ "version": "1.0.3", "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/etag": { "version": "1.8.1", "license": "MIT", @@ -1954,14 +1270,6 @@ "node": ">=14" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "license": "ISC", @@ -2120,14 +1428,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "license": "MIT", @@ -2323,6 +1623,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, "node_modules/jose": { "version": "4.15.9", "license": "MIT", @@ -2330,22 +1635,6 @@ "url": "https://github.com/sponsors/panva" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "2.5.2", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/json-bigint": { "version": "1.0.0", "license": "MIT", @@ -2354,17 +1643,6 @@ "bignumber.js": "^9.0.0" } }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "license": "MIT", @@ -2495,14 +1773,6 @@ "license": "Apache-2.0", "optional": true }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/lru-memoizer": { "version": "2.3.0", "license": "MIT", @@ -2573,27 +1843,18 @@ "node": ">= 0.6" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ms": { "version": "2.0.0", "license": "MIT" }, - "node_modules/nanoid": { - "version": "3.3.7", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/negotiator": { "version": "0.6.3", "license": "MIT", @@ -2627,11 +1888,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-releases": { - "version": "2.0.14", - "dev": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -2696,42 +1952,18 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "0.1.10", "license": "MIT" }, - "node_modules/picocolors": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.4.47", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/prisma": { "version": "5.20.0", "devOptional": true, @@ -2828,14 +2060,6 @@ "node": ">= 0.8" } }, - "node_modules/react-refresh": { - "version": "0.14.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "license": "MIT", @@ -2878,40 +2102,6 @@ "node": ">=14" } }, - "node_modules/rollup": { - "version": "4.22.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", - "fsevents": "~2.3.2" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "funding": [ @@ -3046,6 +2236,25 @@ "@img/sharp-win32-x64": "0.33.5" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.6", "license": "MIT", @@ -3161,14 +2370,6 @@ "version": "2.1.2", "license": "MIT" }, - "node_modules/source-map-js": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/statuses": { "version": "2.0.1", "license": "MIT", @@ -3231,17 +2432,6 @@ "license": "MIT", "optional": true }, - "node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/teeny-request": { "version": "9.0.0", "license": "Apache-2.0", @@ -3313,14 +2503,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "license": "MIT", @@ -3371,40 +2553,6 @@ "node": ">= 0.8" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.0", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/update-browserslist-db/node_modules/picocolors": { - "version": "1.0.1", - "dev": true, - "license": "ISC" - }, "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT", @@ -3435,64 +2583,6 @@ "node": ">= 0.8" } }, - "node_modules/vite": { - "version": "5.4.8", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "license": "BSD-2-Clause", @@ -3526,6 +2616,20 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "license": "MIT", @@ -3604,11 +2708,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, "node_modules/yargs": { "version": "17.7.2", "license": "MIT", diff --git a/web/.env.sample b/web/.env.sample index 87ad24dd..a514b4d3 100644 --- a/web/.env.sample +++ b/web/.env.sample @@ -1,14 +1,14 @@ -VITE_API_ENDPOINT=http://localhost:3000 -VITE_FIREBASE_API_KEY=example -VITE_FIREBASE_AUTH_DOMAIN=example.com -VITE_FIREBASE_PROJECT_ID=example -# VITE_FIREBASE_STORAGE_BUCKET=example.com -# VITE_FIREBASE_MESSAGING_SENDER_ID=example -VITE_FIREBASE_APP_ID=example -VITE_FIREBASE_MEASUREMENT_ID=example -VITE_ALLOW_ANY_MAIL_ADDR=true +NEXT_PUBLIC_API_ENDPOINT=http://localhost:3000 +NEXT_PUBLIC_FIREBASE_API_KEY=example +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=example.com +NEXT_PUBLIC_FIREBASE_PROJECT_ID=example +# NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=example.com +# NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=example +NEXT_PUBLIC_FIREBASE_APP_ID=example +NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=example +NEXT_PUBLIC_ALLOW_ANY_MAIL_ADDR=true #supabase -VITE_SUPABASE_URL= -VITE_SUPABASE_KEY= +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_KEY= diff --git a/web/.gitignore b/web/.gitignore index a547bf36..0876a4e4 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -22,3 +22,9 @@ dist-ssr *.njsproj *.sln *.sw? + +.next +next-env.d.ts +out + +/common diff --git a/web/Dockerfile b/web/Dockerfile index 458eef88..7a223471 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /coursemate/dev/web COPY package.json package-lock.json ./ RUN npm ci -ENV VITE_API_ENDPOINT=http://localhost:3000 +ENV NEXT_PUBLIC_API_ENDPOINT=http://localhost:3000 COPY ./ . CMD npm run dev -- --host diff --git a/web/src/api/chat/chat.ts b/web/api/chat/chat.ts similarity index 99% rename from web/src/api/chat/chat.ts rename to web/api/chat/chat.ts index c161e1ba..a574b973 100644 --- a/web/src/api/chat/chat.ts +++ b/web/api/chat/chat.ts @@ -9,7 +9,7 @@ import type { SharedRoom, UpdateRoom, UserID, -} from "../../common/types"; +} from "../../../common/types"; import { ErrUnauthorized, credFetch } from "../../firebase/auth/lib"; import endpoints from "../internal/endpoints"; diff --git a/web/src/api/chat/hooks.ts b/web/api/chat/hooks.ts similarity index 62% rename from web/src/api/chat/hooks.ts rename to web/api/chat/hooks.ts index d7737afb..d835cb9c 100644 --- a/web/src/api/chat/hooks.ts +++ b/web/api/chat/hooks.ts @@ -1,16 +1,22 @@ +"use client"; + import { useCallback } from "react"; import { z } from "zod"; // import { useCallback, useEffect, useState } from "react"; -import type { Message, RoomOverview } from "../../common/types"; -import { MessageSchema, RoomOverviewSchema } from "../../common/zod/schemas"; -import { type Hook, useSWR } from "../../hooks/useSWR"; +import type { Message, RoomOverview } from "../../../common/types"; +import { MessageSchema, RoomOverviewSchema } from "../../../common/zod/schemas"; +import { type Hook, useCustomizedSWR } from "../../hooks/useCustomizedSWR"; import type { UserID } from "../internal/endpoints"; // import type { Hook } from "../share/types"; import * as chat from "./chat"; const OverviewListSchema = z.array(RoomOverviewSchema); export function useRoomsOverview(): Hook { - return useSWR("useRoomsOverview", chat.overview, OverviewListSchema); + return useCustomizedSWR( + "useRoomsOverview", + chat.overview, + OverviewListSchema, + ); } const MessageListSchema = z.array(MessageSchema); @@ -21,5 +27,5 @@ export function useMessages(friendId: UserID): Hook { async () => (await chat.getDM(friendId)).messages, [friendId], ); - return useSWR(key, fetcher, MessageListSchema); + return useCustomizedSWR(key, fetcher, MessageListSchema); } diff --git a/web/api/course.hook.ts b/web/api/course.hook.ts new file mode 100644 index 00000000..509c2169 --- /dev/null +++ b/web/api/course.hook.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; +import type { Course } from "../../common/types"; +import { CourseSchema } from "../../common/zod/schemas"; +import { type Hook, useCustomizedSWR } from "../hooks/useCustomizedSWR"; +import { getMyCourses } from "./course"; + +const CourseListSchema = z.array(CourseSchema); +export function useMyCourses(): Hook { + return useCustomizedSWR("useMyCourses", getMyCourses, CourseListSchema); +} diff --git a/web/src/api/course.ts b/web/api/course.ts similarity index 97% rename from web/src/api/course.ts rename to web/api/course.ts index bc4bd9d7..17bba9bf 100644 --- a/web/src/api/course.ts +++ b/web/api/course.ts @@ -1,4 +1,4 @@ -import type { Course, CourseID, Day } from "../common/types"; +import type { Course, CourseID, Day } from "../../common/types"; import { credFetch } from "../firebase/auth/lib"; import endpoints from "./internal/endpoints"; diff --git a/web/src/api/image.ts b/web/api/image.ts similarity index 100% rename from web/src/api/image.ts rename to web/api/image.ts diff --git a/web/src/api/internal/endpoints.ts b/web/api/internal/endpoints.ts similarity index 96% rename from web/src/api/internal/endpoints.ts rename to web/api/internal/endpoints.ts index aeb1c67e..53fc770a 100644 --- a/web/src/api/internal/endpoints.ts +++ b/web/api/internal/endpoints.ts @@ -1,8 +1,8 @@ -import type { CourseID, Day, GUID } from "../../common/types"; -import type { MessageID, ShareRoomID } from "../../common/types"; +import type { CourseID, Day, GUID } from "../../../common/types"; +import type { MessageID, ShareRoomID } from "../../../common/types"; -export const origin: string | null = import.meta.env.VITE_API_ENDPOINT; -if (!origin) throw new Error("import.meta.env.VITE_API_ENDPOINT not found!"); +export const origin: string | null = process.env.NEXT_PUBLIC_API_ENDPOINT ?? ""; +if (!origin) throw new Error("process.env.NEXT_PUBLIC_API_ENDPOINT not found!"); // TODO: de-export this and use one from /common export type UserID = number; diff --git a/web/src/api/internal/fetch-func.ts b/web/api/internal/fetch-func.ts similarity index 93% rename from web/src/api/internal/fetch-func.ts rename to web/api/internal/fetch-func.ts index b5e712f5..2ef45789 100644 --- a/web/src/api/internal/fetch-func.ts +++ b/web/api/internal/fetch-func.ts @@ -1,4 +1,4 @@ -import { Err, Ok, type Result } from "../../common/lib/result"; +import { Err, Ok, type Result } from "../../../common/lib/result"; export async function safeFetch( path: string, diff --git a/web/src/api/match.ts b/web/api/match.ts similarity index 100% rename from web/src/api/match.ts rename to web/api/match.ts diff --git a/web/src/api/request.ts b/web/api/request.ts similarity index 100% rename from web/src/api/request.ts rename to web/api/request.ts diff --git a/web/src/api/share/types.ts b/web/api/share/types.ts similarity index 100% rename from web/src/api/share/types.ts rename to web/api/share/types.ts diff --git a/web/src/api/user.ts b/web/api/user.ts similarity index 84% rename from web/src/api/user.ts rename to web/api/user.ts index 64315258..8f61ff2a 100644 --- a/web/src/api/user.ts +++ b/web/api/user.ts @@ -1,10 +1,10 @@ import { z } from "zod"; -import type { GUID, UpdateUser, User, UserID } from "../common/types"; -import { parseUser } from "../common/zod/methods.ts"; -import { UserIDSchema, UserSchema } from "../common/zod/schemas.ts"; +import type { GUID, UpdateUser, User, UserID } from "../../common/types.ts"; +import { parseUser } from "../../common/zod/methods.ts"; +import { UserIDSchema, UserSchema } from "../../common/zod/schemas.ts"; import { credFetch } from "../firebase/auth/lib.ts"; +import { type Hook, useCustomizedSWR } from "../hooks/useCustomizedSWR.ts"; import { useAuthorizedData } from "../hooks/useData.ts"; -import { type Hook, useSWR } from "../hooks/useSWR.ts"; import endpoints from "./internal/endpoints.ts"; import type { Hook as UseHook } from "./share/types.ts"; @@ -15,13 +15,17 @@ export function useRecommended(): UseHook { return useAuthorizedData (url); } export function useMatched(): Hook { - return useSWR("users::matched", matched, UserListSchema); + return useCustomizedSWR("users::matched", matched, UserListSchema); } export function usePendingToMe(): Hook { - return useSWR("users::pending::to-me", pendingToMe, UserListSchema); + return useCustomizedSWR("users::pending::to-me", pendingToMe, UserListSchema); } export function usePendingFromMe(): Hook { - return useSWR("users::pending::from-me", pendingFromMe, UserListSchema); + return useCustomizedSWR( + "users::pending::from-me", + pendingFromMe, + UserListSchema, + ); } async function matched(): Promise { @@ -39,7 +43,7 @@ async function pendingFromMe(): Promise { // 自身のユーザー情報を取得する export function useAboutMe(): Hook { - return useSWR("users::aboutMe", aboutMe, UserSchema); + return useCustomizedSWR("users::aboutMe", aboutMe, UserSchema); } async function aboutMe(): Promise { @@ -49,7 +53,7 @@ async function aboutMe(): Promise { // 自身のユーザーIDを取得する export function useMyID(): Hook { - return useSWR("users::myId", getMyId, UserIDSchema); + return useCustomizedSWR("users::myId", getMyId, UserIDSchema); } async function getMyId(): Promise { const me = await aboutMe(); diff --git a/web/app/chat/layout.tsx b/web/app/chat/layout.tsx new file mode 100644 index 00000000..6925d366 --- /dev/null +++ b/web/app/chat/layout.tsx @@ -0,0 +1,31 @@ +import { Box } from "@mui/material"; +import BottomBar from "../../components/BottomBar"; +import Header from "../../components/Header"; + +export default function ChatPageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> + + + {children} + ++ > + ); +} diff --git a/web/app/chat/page.tsx b/web/app/chat/page.tsx new file mode 100644 index 00000000..e24b11ce --- /dev/null +++ b/web/app/chat/page.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { Typography } from "@mui/material"; +import { useSearchParams } from "next/navigation"; +import { Suspense } from "react"; +import { useRoomsOverview } from "../../api/chat/hooks"; +import RoomList from "../../components/chat/RoomList"; +import { RoomWindow } from "../../components/chat/RoomWindow"; +import FullScreenCircularProgress from "../../components/common/FullScreenCircularProgress"; +import { NavigateByAuthState } from "../../components/common/NavigateByAuthState"; + +function ChatListContent() { + const searchParams = useSearchParams(); + + const friendId = searchParams.get("friendId"); + + const { state } = useRoomsOverview(); + + return friendId ? ( + <> + Chat - friend Id: {friendId}
++ > + ) : state.current === "loading" ? ( + + ) : state.current === "error" ? ( + Error: {state.error.message} + ) : ( ++ ); +} + +export default function Chat() { + return ( + + + ); +} diff --git a/web/app/edit/courses/page.tsx b/web/app/edit/courses/page.tsx new file mode 100644 index 00000000..8fc3ec09 --- /dev/null +++ b/web/app/edit/courses/page.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { Box, Button, Typography } from "@mui/material"; +import Link from "next/link"; +import { useAboutMe } from "../../../api/user"; +import FullScreenCircularProgress from "../../../components/common/FullScreenCircularProgress"; +import { NavigateByAuthState } from "../../../components/common/NavigateByAuthState"; +import EditableCoursesTable from "../../../components/course/EditableCoursesTable"; + +export default function EditCourses() { + const { state } = useAboutMe(); + const data = state.data; + const loading = state.current === "loading"; + const error = state.current === "error" ? state.error : null; + + return ( +}> + + + + + ); +} diff --git a/web/app/edit/layout.tsx b/web/app/edit/layout.tsx new file mode 100644 index 00000000..adb4ac22 --- /dev/null +++ b/web/app/edit/layout.tsx @@ -0,0 +1,29 @@ +import { Box } from "@mui/material"; +import Header from "../../components/Header"; + +export default function EditPageLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> ++ ++ 授業編集 + + {loading ? ( ++ ) : error ? ( + Error: {error.message}
+ ) : data ? ( + <> ++ > + ) : ( + データがありません。
+ )} + ++ + + ++ + {children} + + > + ); +} diff --git a/web/app/edit/profile/page.tsx b/web/app/edit/profile/page.tsx new file mode 100644 index 00000000..1f259962 --- /dev/null +++ b/web/app/edit/profile/page.tsx @@ -0,0 +1,552 @@ +"use client"; + +import EditIcon from "@mui/icons-material/Edit"; +import { + Box, + Button, + FormControl, + IconButton, + InputLabel, + MenuItem, + Select, + TextField, + Typography, +} from "@mui/material"; +import type { SelectChangeEvent } from "@mui/material"; +import { useRouter } from "next/navigation"; +import { enqueueSnackbar } from "notistack"; +import { useEffect, useState } from "react"; +import { update, useAboutMe } from "../../../api/user"; +import type { UpdateUser } from "../../../common/types"; +import { UpdateUserSchema } from "../../../common/zod/schemas"; +import FullScreenCircularProgress from "../../../components/common/FullScreenCircularProgress"; +import { NavigateByAuthState } from "../../../components/common/NavigateByAuthState"; +import { useAlert } from "../../../components/common/alert/AlertProvider"; +import PhotoModal from "../../../components/config/PhotoModal"; +import { PhotoPreviewButton } from "../../../components/config/PhotoPreview"; +import UserAvatar from "../../../components/human/avatar"; +import { facultiesAndDepartments } from "../../signup/data"; + +export default function EditProfile() { + const router = useRouter(); + const { showAlert } = useAlert(); + const { state } = useAboutMe(); + const data = state.data; + const error = state.current === "error" ? state.error : null; + const loading = state.current === "loading"; + + const [name, setName] = useState(""); + const [gender, setGender] = useState(""); + const [grade, setGrade] = useState(""); + const [faculty, setFaculty] = useState(""); + const [department, setDepartment] = useState(""); + const [intro, setIntro] = useState(""); + const [pictureUrl, setPictureUrl] = useState(""); + const [tmpName, setTmpName] = useState(""); + const [tmpGender, setTmpGender] = useState(""); + const [tmpGrade, setTmpGrade] = useState(""); + const [tmpFaculty, setTmpFaculty] = useState(""); + const [tmpDepartment, setTmpDepartment] = useState(""); + const [tmpIntro, setTmpIntro] = useState(""); + + const [isEditingName, setIsEditingName] = useState(false); + const [isEditingGender, setIsEditingGender] = useState(false); + const [isEditingGrade, setIsEditingGrade] = useState(false); + const [isEditingFaculty, setIsEditingFaculty] = useState(false); + const [isEditingDepartment, setIsEditingDepartment] = useState(false); + const [isEditingIntro, setIsEditingIntro] = useState(false); + + const [errorMessage, setErrorMessage] = useState(""); + + const [nameError, setNameError] = useState (""); + const [genderError, setGenderError] = useState (""); + const [gradeError, setGradeError] = useState (""); + const [facultyError, setFacultyError] = useState (""); + const [departmentError, setDepartmentError] = useState (""); + const [introError, setIntroError] = useState (""); + + useEffect(() => { + if (data) { + setName(data.name); + setGender(data.gender); + setGrade(data.grade); + setFaculty(data.faculty); + setDepartment(data.department); + setIntro(data.intro); + setPictureUrl(data.pictureUrl); + setTmpName(data.name); + setTmpGender(data.gender); + setTmpGrade(data.grade); + setTmpFaculty(data.faculty); + setTmpDepartment(data.department); + setTmpIntro(data.intro); + } + }, [data]); + + function afterPhotoUpload(result: string) { + try { + setPictureUrl(result); + handleSave({ pictureUrl: result }); + } catch (err) { + console.error(err); + // probably a network error + onPhotoError(new Error("画像の更新に失敗しました")); + } + } + + function onPhotoError(err: Error) { + enqueueSnackbar({ + message: err?.message ?? "画像の更新に失敗しました", + }); + } + const [open, setOpen] = useState (false); + + function hasUnsavedChangesOrErrors() { + return ( + isEditingName || + isEditingGender || + isEditingGrade || + isEditingFaculty || + isEditingDepartment || + isEditingIntro || + errorMessage || + nameError || + genderError || + gradeError || + facultyError || + departmentError || + introError + ); + } + + function handleGoToCourses() { + if (hasUnsavedChangesOrErrors()) { + showAlert({ + AlertMessage: "まだ編集中のフィールド、もしくはエラーがあります", + subAlertMessage: "本当にページを移動しますか?変更は破棄されます", + yesMessage: "移動", + clickYes: () => { + router.push("/edit/courses"); + }, + }); + } else { + router.push("/edit/courses"); + } + } + + function handleBack() { + if (hasUnsavedChangesOrErrors()) { + showAlert({ + AlertMessage: "編集中のフィールド、もしくはエラーがあります。", + subAlertMessage: "本当にページを移動しますか?変更は破棄されます", + yesMessage: "移動", + clickYes: () => { + router.push("/settings/profile"); + }, + }); + } else { + router.push("/settings/profile"); + } + } + + async function handleSave(input: Partial ) { + setErrorMessage(""); + setNameError(""); + setGenderError(""); + setGradeError(""); + setFacultyError(""); + setDepartmentError(""); + setIntroError(""); + const data: UpdateUser = { + name: (input.name ?? name).trim(), + gender: input.gender ?? gender, + grade: input.grade ?? grade, + faculty: input.faculty ?? faculty, + department: input.department ?? department, + intro: (input.intro ?? intro).trim(), + pictureUrl: input.pictureUrl ?? pictureUrl, + }; + const result = UpdateUserSchema.safeParse(data); + if (!result.success) { + result.error.errors.map((err) => { + switch (err.path[0]) { + case "name": + setNameError(err.message); + break; + case "gender": + setGenderError(err.message); + break; + case "grade": + setGradeError(err.message); + break; + case "faculty": + setFacultyError(err.message); + break; + case "department": + setDepartmentError(err.message); + break; + case "intro": + setIntroError(err.message); + break; + default: + setErrorMessage("入力に誤りがあります"); + } + }); + return; + } + await update(data); + } + + function handleEdit(setter: React.Dispatch >) { + setTmpName(name); + setTmpGender(gender); + setTmpGrade(grade); + setTmpFaculty(faculty); + setTmpDepartment(department); + setTmpIntro(intro); + setIsEditingName(false); + setIsEditingGender(false); + setIsEditingGrade(false); + setIsEditingFaculty(false); + setIsEditingDepartment(false); + setIsEditingIntro(false); + setter(true); + } + + const handleFacultyChange = (event: SelectChangeEvent ) => { + setTmpFaculty(event.target.value); + }; + + const handleDepartmentChange = (event: SelectChangeEvent ) => { + setTmpDepartment(event.target.value); + }; + + return ( + +