From 0f85f5e9b235711b33800f573278b28387d63a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Mon, 26 Jan 2026 14:23:52 +0100 Subject: [PATCH 1/9] more hosts --- lib/apollo-client.ts | 5 ++- lib/env.ts | 3 ++ lib/hosts.ts | 67 +++++++++++++++++++++++++++++ package-lock.json | 19 +++++--- package.json | 2 +- public/gift-collective-logo.png | Bin 0 -> 18476 bytes public/raft-logo.png | Bin 0 -> 38000 bytes public/social-change-nest-logo.png | Bin 0 -> 11662 bytes 8 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 public/gift-collective-logo.png create mode 100644 public/raft-logo.png create mode 100644 public/social-change-nest-logo.png diff --git a/lib/apollo-client.ts b/lib/apollo-client.ts index 883361b..eeb4e08 100644 --- a/lib/apollo-client.ts +++ b/lib/apollo-client.ts @@ -6,7 +6,7 @@ import merge from 'deepmerge'; import isEqual from 'lodash/isEqual'; import { GetSessionParams } from 'next-auth/react'; -import { PublicEnv } from './env'; +import { PrivateEnv, PublicEnv } from './env'; export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__'; @@ -33,7 +33,8 @@ function createApolloClient({ context, fetch }: { context?: GetSessionParams; fe ...headers, // authorization: session?.accessToken ? `Bearer ${session?.accessToken}` : '', // eslint-disable-next-line no-process-env - ...(process.env.OPENCOLLECTIVE_API_KEY && { 'Api-Key': process.env.OPENCOLLECTIVE_API_KEY }), + ...(PrivateEnv.OPENCOLLECTIVE_API_KEY && { 'Api-Key': PrivateEnv.OPENCOLLECTIVE_API_KEY }), + ...(PrivateEnv.OPENCOLLECTIVE_PERSONAL_TOKEN && { 'Personal-Token': PrivateEnv.OPENCOLLECTIVE_PERSONAL_TOKEN }), }, }; }); diff --git a/lib/env.ts b/lib/env.ts index c3d6ba8..e455174 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -38,4 +38,7 @@ export const PrivateEnv = { /* A random string used to encrypt JWTs */ NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, + + OPENCOLLECTIVE_API_KEY: process.env.OPENCOLLECTIVE_API_KEY, + OPENCOLLECTIVE_PERSONAL_TOKEN: process.env.OPENCOLLECTIVE_PERSONAL_TOKEN, }; diff --git a/lib/hosts.ts b/lib/hosts.ts index bb2f182..38cebe6 100644 --- a/lib/hosts.ts +++ b/lib/hosts.ts @@ -53,6 +53,7 @@ export const hosts: { 'the-social-change-nest', 'ocnz', 'giftcollective', + 'raft', 'numfocus', 'platform6-coop', 'wwcodeinc', @@ -127,4 +128,70 @@ export const hosts: { includeCategoryTags: [], excludeCategoryTags: [...defaultExcludeCategoryTags], }, + { + name: 'Gift Collective', + slug: 'giftcollective', + currency: 'NZD', + startYear: 2020, + logoSrc: '/gift-collective-logo.png', + website: 'https://opencollective.com/giftcollective', + color: { hex: '#6B9E9E', closestPaletteColor: 'teal' }, + styles: { + text: 'text-[#6B9E9E]', + groupHoverText: 'group-hover:text-[#6B9E9E]', + button: 'bg-[#6B9E9E] text-white', + brandBox: 'lg:bg-[#6B9E9E] lg:bg-opacity-5 text-[#6B9E9E]', + box: 'bg-[#6B9E9E] bg-opacity-5 text-[#6B9E9E]', + border: 'border-[#6B9E9E]', + }, + groupTags: { + ...defaultGroupTags, + }, + includeCategoryTags: [], + excludeCategoryTags: [...defaultExcludeCategoryTags], + }, + { + name: 'Social Change Nest', + slug: 'the-social-change-nest', + currency: 'GBP', + startYear: 2019, + logoSrc: '/social-change-nest-logo.png', + website: 'https://opencollective.com/the-social-change-nest', + color: { hex: '#EC008C', closestPaletteColor: 'pink' }, + styles: { + text: 'text-[#EC008C]', + groupHoverText: 'group-hover:text-[#EC008C]', + button: 'bg-[#EC008C] text-white', + brandBox: 'lg:bg-[#EC008C] lg:bg-opacity-5 text-[#EC008C]', + box: 'bg-[#EC008C] bg-opacity-5 text-[#EC008C]', + border: 'border-[#EC008C]', + }, + groupTags: { + ...defaultGroupTags, + }, + includeCategoryTags: [], + excludeCategoryTags: [...defaultExcludeCategoryTags], + }, + { + name: 'Raft Foundation', + slug: 'raft', + currency: 'USD', + startYear: 2021, + logoSrc: '/raft-logo.png', + website: 'https://opencollective.com/raft', + color: { hex: '#C4B454', closestPaletteColor: 'yellow' }, + styles: { + text: 'text-[#C4B454]', + groupHoverText: 'group-hover:text-[#C4B454]', + button: 'bg-[#C4B454] text-white', + brandBox: 'lg:bg-[#C4B454] lg:bg-opacity-5 text-[#C4B454]', + box: 'bg-[#C4B454] bg-opacity-5 text-[#C4B454]', + border: 'border-[#C4B454]', + }, + groupTags: { + ...defaultGroupTags, + }, + includeCategoryTags: [], + excludeCategoryTags: [...defaultExcludeCategoryTags], + }, ]; diff --git a/package-lock.json b/package-lock.json index 7031e9f..41320b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6086,9 +6086,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001431", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", - "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "funding": [ { "type": "opencollective", @@ -6097,8 +6097,13 @@ { "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/capital-case": { "version": "1.0.4", @@ -18854,9 +18859,9 @@ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" }, "caniuse-lite": { - "version": "1.0.30001431", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", - "integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==" + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==" }, "capital-case": { "version": "1.0.4", diff --git a/package.json b/package.json index 5bae917..79e9c72 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint": "eslint . --ignore-path .gitignore", "lint:fix": "npm run lint -- --fix", "lint:quiet": "npm run lint -- --quiet", - "prettier": "prettier \"*.@(js|ts|jsx|tsx|json|md)\" \"@(components|lib|pages|scripts|server|test|stories)/**/*.@(js|json|md|mdx)\"", + "prettier": "prettier \"*.@(js|ts|jsx|tsx|json|md|mdx)\" \"@(components|lib|pages|scripts|server|test|stories)/**/*.@(js|ts|jsx|tsx|json|md|mdx)\"", "prettier:check": "npm run prettier -- --check", "prettier:write": "npm run prettier -- --write", "start": "next start", diff --git a/public/gift-collective-logo.png b/public/gift-collective-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ca28def0718e9d99d4e9212fba44f4361e271e78 GIT binary patch literal 18476 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 literal 0 HcmV?d00001 diff --git a/public/social-change-nest-logo.png b/public/social-change-nest-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2685c0083d5b8ef2366baf052ebc45c743d69750 GIT binary patch literal 11662 zcmYLvbzIZm_y0CF8l)vfk%1NkDKjYm0HDy(RyP3vfcU>a05Ku{VeRwY z8UG;h(zfsg0LW?AjyEn%(%Gr3s0i z=%A&OjyOuWXAYa~#wy-0qN#C>N{qHdM1EMLXHBoBO?gFp2l0y|czPd=PHem4nu zW%1HhV7HmtdoEx}(^1ne;LyY-yJ}Q+{y5@CwoNuYlQ$*;6HXt)ZIA(Ug35m+U!gwP zO$f)nicAMJEmtcRd7iwly4t`w&yHQ+EI5-byE_v2tv;WAlqIM!Tm<80xYu+fusO`s z|3TQiuku!<^<*bJNk1g9&h5A|%AdeRd= zqq7m-tB~u*d?!$)F>c$u*hV z8A8R}gwBs2&bO~!o%dDj?=H@Fxw&Sj>)SeXfzwSPm{Lro)ON6$Btuok)vvSA15ugT zsWwY=2sM>z{|!PCol-CzHaq;{F%fT>z}{&#YHhX)x4L~sFI1s4=qnJX_o-n-K#o`g zVn=-%xO!AM(x+%Tk4%Si4C@Qesd9*lAWDm8P_nmAl>p#g@zq{U#nX;Jm5NM+2J9lmje4^Xpwz6Oo z(k?)qxUuAtC9F6#{~49WrNVP_cJJ!? z8l89RgErTqT|Ue%mUOA6t;8SeSrVz)yHG3yZ3E4|V?JTnmE13UvDUZnQ&@84iPYC! zt6HnH-Mmv)bd~Pn7fHsTA2L5f|4@3fL^)QJ??v^fU$Y*8B_Xp5#3RcuucVFI{Ma)o zO-H*}{fo0twd`2PM#0P8gAKRuC3V&u*Kg$>ji?b9Iw6i3cWbXp^te^T8~A#)&n&+A z@}&1=t2#}FPA>vg3Qp=(^sa24vpNL}x=JUM-FH}-GnOP?U3^%A^yhRX)Zd$1mY|Zg z!8^6pH}g|hDw(%3z}~kvMR}t;3CJ{qgL+Q{Fr}$IALVDZf1oQ$W|hRn)2irm^(z5L z3X%8M_RITzRj|pADk^1bGXQ)2-!{1Qbb{i_^KEv;QQZd{N#6&gZ44U2wid2)FZz08 znHFmv1RXX6*M*N+X)#_}g?!=WzTey9oDJKH5Nn)!cp)#<5oqkH5s+Iw+CQSzJV7$e zMW8eM>~-VG7Th2#H-O2dojT`C9oDLvLGt>{YpdexrcGBskU_}14=0XmypxJ(mv~IZ zdTq|e&4WC6_${P7b!+l-EC@}QT_t=enLb_^NzNbs5`m#2t5D$VAqRTiMLq}{Ter6# zEuDBP7sbRgSd>PNO|Vd7UxIAk_|5Xoom|)FjcxtY54<35KP@rG#yz*F zC4vtOllQ8Jrw=zn?p<4Cn-ZCIhgiP@tt?~6M=ysogk=4zi~Ezhh_`AU;fhU(9Vy=G zM-f*Z@5a>82jz%CeYyzURTp(9yELc|+qge7TqzK+Y!U|=L^RGh)`3 zujdM`qnT)kaQ0g?VHuC@dHJ~c?(K#5t6@HD>9^BlNVNPn!@9ZyZA0enK=_?7TS>vK z)Jr=Ict}RZlMl@evNn5JmNly)>&Y~Iqa#^8?N1*YSE!7Ek}vvfkON_Dl&18g))I_R z=Yp?zn~&PN`>iC!r-h0-4KpNeIFZ8R!s(kr0?Vx zY?1z9ZBHx-5C=s2T@s@0)zbObk|)|_Su^r z7#fQ{K74bLpBPO1i7?a6podg-Il$}0sWE1pua}POMp1&@=}s+#(^DadtEgw5SDj+5 z6Y>!{HNZQmuu0rt#xEn&nI8V(;;^MpOJgXMWxaDbCG~8Pme`t*7*dE^SP`s9vREVW znWZ7od+0O|ukij_P%5MP&D!EE^<^|KRUn^CyqZ>iey;`cDyShm0qid}xPH77@NhoP zo&MU;+rlu_f#~R$(W~o$OEuQrSv)fj_pSwCzYCN(UN92eQZ@b%ok|rV>O`g0Mb);<+mIg91{rz$m@fa zcTf4h8)`GWu&&7f%O(4`t}v#f{j*Wr*+@YrgFZv;vkngQ0f`*BX}K?FB%4XNayR>_ z8aLI6Fxsx2HE&cs8tkRV)(KV(3TN7ODJbS?Jnd81AkuCe7*g---s_{= zOO-GZBY<>O%x&I@6ug7rPvSw-EcC@tRC-)(Jy__|kii&VeQM&tjgj*}cq9m9JOYz3 zpV)E|i|na}CjyQ9MC3h{c(sCzJCvWpR@F2*Dq5<2S3wJf)z2d_@({|IJX^qVfJmU-UdZktQi1Z>DO5(eiEJyPnQ6<=mSCwK z37U_9V54?uTRGXaZ$vAdx}qh3rvv1p*(yYS9Yf}Yi0G(xEt(k_I_suc<5LPkIZLHF z1IM~ApzTrh;8(}gvQ9|^R4NH z8LWcLon44`-y!-MiY|~SqwN941l!YEL*@L;34PJ7we|XT#?LaCeG682Gry>@;!ygD zw`+h7voJQ>=jFh;`{ca$bs#2Nh1HK~V!j2EzgY-5{-ICnCY*toAjSGkQ{0b2s@3m2 z-Q89?eC}d8Q8EVzou_Y!K^!k}2F;G>C+hxAOHsQmbp5KXQuwCkOD+E&bDq){R}9&w z!SbC@Y!u;9z+<4xp5nsA@guJxL#ps6(lXJrQ!K_9aok%U!U z2)S|$A_CD}7F}Wq)nHH!Zt%_X<4KR~I4j#NS}Ir$$&SS4ifUWTb8eLwbbpIaiZ!;e zdNW8~>1^aq1lq{VM&f5+%)5BHsTDS*@7C8{Fd{u;TJm(b|g!E{#PD)2?B4%$_*7dpCMF&L#&8tn{oj|6JHn+%iqCBuHy;%lL zcOb%?-RnNQZmHs;CQj+utU-GI_;nt{ZIW6*sP9bkw$rJF|IrP}&B$e&BPrfs7Hh+QTZ2o z8R$+Grkd8P{rBup^-Udpoosu^)=}SfrPo`kv1a(zmB0BR4x*;W)ok$r3czi?0JNMB zk4XRv6GEu57bcY$yV-#ePX;lhHw~w9cQ92T%U6v%cd4(x51~*~1I*TX#`uY-uI4yv zhlOt!$jo+|4b!F6f8*5$+si{Kt@-H{n-I;MFFNtQ408FcaayAyMv=g+w$lhkg;53~ zmHZ&4x*beFjdmcXTlW!@60|Hjod&^YX|8UviS!4-nw2)H_mZw}E@hL!Sf!UbiC3-- zVr?Qg_iRANL*@uforwy_uLAa-(BPZp4St}c&y{biGV98PgV+yg3NT{Hu3F}(B>bM0 z2l%|hrTk!qz_7Sdh4%-y*RIHK*uCbWyR@E|0p2+y!-hNT|H#(O?|XWyk}UgMrP3EA?6{Sp zzQIXC@abcU=4iwe)ozah))rsa`kX7|ijrqL6j0mw^ve zrs|X0Qs0f=d%7R@Xju}Yul<}vp5GE(MWI`M+Mb?$nR=-6LC$_@+>Gk28B!WU%%Ox(Dk zssqqtjmztMcESCEf+-S|;#h7~@&zq0P0!4Io?d2IbDn-U!vpgBhXKjv7MgX`p*Ab-aL{bLF{4d5ITzZ!cMsNZ$Y^p$GWq0c75sDzw%IQI*p-!d| zmay_X5yDv#lE|L)Oc_7umq4@9LBQ2Ro!)-pqQ|7~$T>?=t7c;<=bdO=hfeAUSBo%U z)=hmdtXj|vtr(&6L=!ed#t1U59=f)P<<6SF#w~=dwp)G&FS&{bXct#Q&f8IX?g?IL zG;~1@6jEC;enBRUs9Co#>S;=c-Sa;%7>f=!tb9`sAPXi?moTjid4w$dOi%TeYK!uq zOC5~+GtEb!+cuBq+yfJ$ILW9;wl&ktL;}gEh=b>B96rp~b%?L1@$)!|Vn4Icu#tBF zn-wZNKklOT(+YSZyZDu7#~K)o;yq^nLn)gGclOYe^lsEF0Ci_rPOxl5-uiX; zIj$%b{8!#61>*t65v^vCp#{?8pH^{NcsmfG6fjw3Fy?mJDXkZK`o}cckI~9D1uO1| zk{Zqu`A`FoR?0?T%1p$!GfznzZTdMc2%7s0_jN5XW@C%%gFF2?$RCi0;^t=7|Tv-Y@YcJx(F9$INzvl4X_ zf=?r~`FCy*9Jkg21|DBMcxlL2eHn~!D@;EC(f$OTfEVNvNjWP}t2dtP+0tgf?60Pq zYehPC>P0vaHaUY?Kf;#uE3eOUVQ%DGWge5?we5rWbh8kJ>W_6L1=xU&Wb1-xpD45* z>n#J{85~#>&fQ3+6yePVJ9@%!*b<&61TTLnFVjq(-2tbO?$M3nF1`>9nYzL9PXrgX zT^DVjdA5}kkllg+P}Q0mg~~zLf{J}ES@Zm|2F0IazCBPt)}`GlQXi(h9e6qEjx~WE8|~#O0Rt0JlGu= ziq%D+a@exeD|f}caMiRU21`^kJrRd#W+-VAFBHDb)5Pc z9$pipU(sAu$S{@;<*VWkCzCpSm+HrpWXU$fO<`2|>AWLapf7gRfqjQM7%=!3?8^@w zm4$QM@ZHNtuP^bQbZT*l*%XQZ2P!l8o+bsz>A^!W111Zn?-r~tlbd4EtJ-%o7SL?s zlu^$OM>j*hb1z5}9gekB#;^eTROfNhBKn_k$|EFDY`e5irD3!2j^$+QFA=B1cqe|W zEWHjz;A`S%e;3PZ=aC!ZCWM51R z+zaI^i8~TdS>wwE&g8#dQ2pL4p)DZ&O@eOy9&Rjp1qMvU?hv)W!mj!Ps`JeZ-LE2e zXkkSc0XWEs?L^yGnU`(!7%f|J{E`1%JALKly@gvMrw_mTWRSopNqQf$EF#jwn7t8e zTc}qtY5K!k%AANj=M8-k(5{4|k?=?R>^e1D;w8l#kmx*{_gy10EOs+R($&Nb*u}>B zT{_gHox)WQ$AACaQ&ls97MMJLS`kJGC%ILZ^g8vP%bwhG#6f>ah9^Wu3D{J5Bk-Fx z0q|tIbxby8A;JNm-CEB;M3MnXt1Q69UPlCc+~dEuN9;-+A7jpon`^wPMJW+Bl`I(& z>52n;?!RhkznHAQ2fe{BH1)ZJh8>X452o6>sB0l_BI>`F)1`LGavZ^rZD6R6n3SugzR2J?- zHL!RczVsf?hl3{g5WCGnN@ioiQiOX=UxbOl!JKDu{+<&}shK&dXHOng)nICDHzkICfo9?=2VD#FZ&ezIR#b?%ghO3~j$ z<4+^zQn4S0Gk%kE&x9lZ0$B+nCz<1IH<5I6PNC9!YOwDH0N;23C$;n^@Paht5bh%I zdZb>ossx~4e-aOP9YIK=4p^NqAo5AfX6I|NvC{xksd0efZD*d)0(K>ZV(1SyP40q6 z;i2k)Z^{HDoCu%f>8L}bK$(7{;%yCBN~ac>>QW*N3gYX24uGpPlJp~x=r#Z+;spsc z?DtY}E@mEaRRKTod1;!QNXTVrUwu)GNsbTvtFqL9F07_yn zn{AE1C;$0i2myumn=p$$R|Qiw*Z=xNfHeSaN_JJU$*_5#ml@dcV=%BN;v&{*Rp{Kq zvQAxeT@{@SJ%I{KQYta@%mu0fHhShM0y7Jv%Zn_0<6Ok}SC4!E>D1)}`EQw8f^MV# zbQ#=6GvIqAQZcn3>VlhF)d71n_$74epTnpwHCajgZ>4~fUj5PZ@mCT=-KII-R#qx% zDh_;jyCmi9yecXi5v4c&D8vbh|KBhe-Jvi2oJXMsQFO;YAz)Fkzxir^;;d&_>@&iD z6ZWMNZhj0^WyA1Hq68e=~xgcK&5;{-rHRt9@V0S@L5a!SJtD zzyK1N8=+kIig5(wnf2||^-*(`Z&)IHhQn)ljS~S#6eX%xtd|QN-+Wt10 zPpSSFgYwsDY^j|MSp4@kWs=Fwxv~J&yrh_Uf8Sm!8%gQM^r{AjFHd!zhFWZ6M0e zJsMy?kKF3^wN#{!R>6Z!L=EVmet#kCeC{*F;)U%g)Cb8peOCMYD|In4G64hrr@t>D zjP5rPbD#2Sp@E=Wg#W?Eds$%GziablspNZ^6T*TLPcwxv?GeMW1`ZtW8nqEb;>{6F z3|JyACMPwDjSR4KIR~eC(!h4H%Bs(W5EI1zxnT;RkCG6e5wWeV1K^T;n`k^eBL0aX(E8y+I- zGy%4(fVEFD8SD2jI74?<#D5xe-~ljTiJr}2XC6ECZUK-h+@lJ*{HA^#O#K%G2Vk0w z=q?cO>)2B`MF#fG`uR^8z+dzO&v0fzWCzM=#>$lT$D>Nov#JRx1pk07fTUHy25y|4 zJOvNO6KnD{P{-vt-4KH_qFR9^iPjQHw#bs)SM zGJP9Y_k_SqXkij;2BG;2NBSvdqRB~XtNxVz3Gsbnqt<#_{dn~T;6GD1Ug5%ho9FIJ zBLaR5Jzs94&7$P~>+?Bl>~nW7&z^QFPx}&VoC_;wt=CqmQ;^`D{7*HxnO*$MTCZ${NM5@ zfIYKzbb>j03HzyebdoM7?P;Hi5>yyqf=}^&=1r?bNUmUnCk{yd;Q|24^KK(BOiD^+ zjdwW#X|-f)Y*iKio5^O?&gn@GC}LWVY4zIcis8e??+oH_I?K`JoOJ{KzipaFOG$WjJMjzX>?`Tv=`)2JC4o zl4gK6`{S!Jf5#M4g87gC^voaH#ziMhM#}LQH}LJ*Fj79 zYpoW;3poE~)}osP%5nnc9tZC$e%%D1?s^RkpQ{gfRZ+?^6h!@9aIxX(yJrdcL#K~x z-?V?7@L_t$%Kx}?UEBLFnj>MObt4w0Tw<4XQqojMHZ8~kX*)lvv zb#y^R6LFkvQ;6dl&Ws>xR&73B!}4#Cp+7&rID)%8^%HrN{;)OjThrb5GXm2wT-?Ba z5&{MSR(TW}Yn=E|Y}7_*U&x8|rssB9q&MOx=zmJWPcI!>lEqp=JXGFBqP7hv1x&A> zmXK)BQvLNS(M~Ybs3bqb?=gJ*!Oax0rS7Yb)1~@H0$?sas=vF5=#R{BeDy<`71gA+ zVVOLDs{{{7N`?H4n3Nq!cq{eSNa#^MREd0f4^uwAk#POaNW@YNSU~x?3(4mRdi?1C zAAzFT#i&`QOD%ZagIke7`xkBLuWjDX{MavpMFYsakfC>!U^{?`$JL1OWH;7bq)W1wp}j68`BX0$&QXpPkuvM%rYf^;+ZK6-Y=E?><>%peKfahn=F%^>l?0 z4J15>a}#@D@+A5id{P{gR&xGmVP!yia_&b!jGurQs1MO?iO<)i@Fs*7Lv0pd%CEBa z9DTZacfop3Muy9fn=Eh;6-bjm6^#xN*q68&15m9Lfl~}sAPLv^xs{@M(WjG{3)cIL z*q~2bi<-!Y9N935D@zWbTf&3YYS&uh7MI&|$@+-C-ZJ*lq6U+z5s zM}0rjWViZ;h3`sF7S?jn;&%$^8^boIs#}H(oK#t3QN0yk6ehbBWXmKzq-%cgDrw;U zZgL;Q;cP##jAR1gn`n35?GwrB{dOU=hq?v4Fz+vdrUULfM+d2(m?t#c)HixzQevl9 zI;qt1PgdP0MHbkk4R!$cHD8wt9VUehG>+p#(oi5OTz*QDlJ%c>nh88mb+0N>A6;6 z9B&dosk9hV<+bw0w~A+@jg?tJk%n9sDMPis#Zl85pF7r|BrGgB(eyXefBbE3uku0p{V*ae00m zm>VjOR4-{@)IrPuRrd$Of0&0$v=AsRYvY@c7w`;(0H3I~b{Ph#vMS9iAMvR4qPU%n z>RsP8EFq}|{iuqzHvA)|3t`vXFVpzpg1=%vkCJAgpsQ|AlT@Sj13aE8K=j=fv4)?u zp4;&YyUfiXsG&UWtv1?_`pC?nuBO;c@T;0r`_uX;{^&2`Bgj(Xu@dS1i?MO`?UtPt zVaZKQ-e%l?9vByHY9t6_0;!g^HX$U}PT~*&Kb2#awZ68wWjQjhli15aFxoCRJ*b_D z-8I^II1|aJEE%^(*m~e--LnWO-qV4!@T>P4bMB}o^_Y|Vo?I3_VVEn>`q!>GY-PZ& zOzWJkywzCbyP@n=&-bV9sT++!c~|d~$pTl~vjn*H_+MDx-5-7y6>b;CejwUF**l+l zFNz4;1uCtx9TjQqQ}H1ozDrU}J_XVbcQ?mVA-vi?W>|-;>~`}BFR*J*ITS`7f+Fw0 z!oW{$686V0TMG(%=UwtS$5~F#_xwqYnC0^6KQ}g6Y$nEX!^QyjX&PG|g}A*hnl+hs zSmp33NJk-)?=7~ag9INcf37L^>Scy*b(CW+V8{Xqi{?A$0gP$4`5gCo)itwX8Z;%o z%}jYW6)Ez40=n~Yx*;k*{Vv?tK5HtP?{~3^7};9k%2YO9`audFL*uLORT)QIj!%BC zU#M_p@k7xk#Hg6YD|quATq>^tt9#C7eDDeFEv`9&EHzE39a}3S5!bpNy~v&6iK<+R zwY>8$uN&u@S|~lTwrjWIi|KV5!_Ueh)6-|*seAN=TS!p_68n6JgvyyMw+i!x+OOr8fM?}E{=&q+6p-K%BFqo$Fq*xqErI6 zcyl8!!F>O5@QP;RT8iE<&hfceOB#zV z@2xvE28$k3%Rp){kHaWguBdEhfI?>lp0dM!UvaH>Fkk60VE^qSnhbAwV19LzLs`E< z^y&{@?>+Z5W}`7^R~m+IVo$L%BJ1#F$yZ+6<&wjm8Q#fnCukir-p$*F%Rz@6lI)kv zAYJyZ{hyzzsdOiSo%?!Wwnn=yvuo?=1MRuczF(PJSol66wNU7LXz-w6b(DWn!EI|j z_Nl~;TXF(?iFB^nB%utpy3%g-cw(3BZ&Mu5pl)iVhP#tD_pRjhR3t5754NE_!U|7b zT$lCWT1&I(#NLf}r2p(w=h9ciHF&|XAnT_nvQc`{v>|4tBySR$!A~W7_>SpTY?#?L zM)#~i$S&Nnn5wk=(|eT>Y8p^_YnH%eQA($LVCtT2qs%kG3dJG$LEotR`E$M;>_|sB zjU9nO2qBqPBIs`X_7uj8w~dYD(r7U|x9=XOABTbMudw;K?3U)b>59_phk{;5Et})` zLqbDZnqKLN8b?=3w9j!FgIs`71gn)DlndsJCds}*P-gOL)xa}y zye@sD3yx<~Dt4JmZTar)&~`r3*0vOGyk~A<@U$JHIX257vI&|G8

IM{vzThz#>v zi*itEJiUPII9G1wwkI;xQ@onJJH~!e(O?Xx z2p0)+Fh36AW%i~VmslT+)!B6>x0m%}a~pYly&$$PSO9rr+T{dM1gY~jse;eRyDB{+lo55W7;OpAI2IzDM^a|0!I@ah zo|cp`bv%C>SrL>bFKx+swfp!mY!7$IW^+v6yk=)bpI`Q?I{!=#<958~zq}OOd-D8+ z%_V@f^3mLLQjxd@6VX&hP2IG*!k?@`+>qqZ1B==>+e(|huN|+hi@4?_NnJ`6n%g-} z?rX}I$_A=iX0n2M_MLPXiVYc^Sfi>tpFOKcF?YRSt7vJB!xiSe=kn zYT@NOaK?5#zo@y$wk_$bma}wjfNV-p;msUoYSbmDH!vxHIS(H9I4-hcFXM`#zZ8RY zKLuW}J-;Nvi~S{+3C||2eMPXCelCf cX5l^#cnZ0dCI Date: Mon, 26 Jan 2026 19:43:47 +0100 Subject: [PATCH 2/9] update hosts list --- lib/hosts.ts | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/lib/hosts.ts b/lib/hosts.ts index 38cebe6..cc71351 100644 --- a/lib/hosts.ts +++ b/lib/hosts.ts @@ -17,6 +17,7 @@ const defaultExcludeCategoryTags = [ 'illinois', 'europe', 'chicago', + 'opencollectiveeu', ]; export const hosts: { @@ -46,22 +47,35 @@ export const hosts: { root: true, name: 'Open Collective', hostSlugs: [ - 'foundation', + // 'foundation', 'europe', + 'oce-foundation-usd', + 'oce-foundation-eur', 'opensource', - 'allforclimate', + 'open-source-collective-eur1', 'the-social-change-nest', - 'ocnz', + 'the-social-change-nest-eu', 'giftcollective', 'raft', - 'numfocus', - 'platform6-coop', - 'wwcodeinc', - 'reculture', - 'fission', - 'ferrous-systems-gmbh', - 'nfsc', - 'xwikisas', + // 'allforclimate', + // 'ocnz', + // 'numfocus', + // 'platform6-coop', + // 'wwcodeinc', + // 'reculture', + // 'fission', + // 'ferrous-systems-gmbh', + // 'nfsc', + // 'xwikisas', + // 'metagov', + // 'foreningen-granslandet', + // 'huddlecraft', + // 'psl-foundation', + // 'pact_collective', + // 'brussels', + // 'vcs-academy', + // 'nativesintech', + // 'thirty-percy' ], currency: 'USD', startYear: 2016, @@ -87,6 +101,7 @@ export const hosts: { { name: 'Open Source Collective', slug: 'opensource', + hostSlugs: ['opensource', 'open-source-collective-eur1'], currency: 'USD', startYear: 2016, logoSrc: '/osc-logo.svg', @@ -109,6 +124,7 @@ export const hosts: { { name: 'Open Collective Europe', slug: 'europe', + hostSlugs: ['europe', 'oce-foundation-usd', 'oce-foundation-eur'], currency: 'EUR', startYear: 2019, logoSrc: '/oce-logo.svg', @@ -153,6 +169,7 @@ export const hosts: { { name: 'Social Change Nest', slug: 'the-social-change-nest', + hostSlugs: ['the-social-change-nest', 'the-social-change-nest-eu'], currency: 'GBP', startYear: 2019, logoSrc: '/social-change-nest-logo.png', From 6632c490853bd7147291e4c0e83ccffecf1fbf8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Mon, 26 Jan 2026 19:44:43 +0100 Subject: [PATCH 3/9] review data fetching --- scripts/fetch-data.ts | 57 +++++++++++-------------------------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/scripts/fetch-data.ts b/scripts/fetch-data.ts index 5a5ef88..64eb564 100644 --- a/scripts/fetch-data.ts +++ b/scripts/fetch-data.ts @@ -29,72 +29,43 @@ dayjs.extend(dayjsPluginIsoWeek); const apolloClient = initializeApollo({ fetch: nodeFetch }); async function graphqlRequest(query, variables: any = {}) { - let data; - // retry fetch 5 times + const maxRetries = 5; - for (let i = 0; i <= 5; i++) { + for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - if (i === 0) { - ({ data } = await apolloClient.query({ - query, - variables, - })); - } else { - console.log('Retrying with half limit, attempt', i, 'of 5'); - const halfLimit = Math.floor(variables.limit / 2); - const { data: dataFirst } = await apolloClient.query({ - query, - variables: { ...variables, offset: variables.offset, limit: halfLimit }, - }); - console.log('first half success'); - const { data: dataSecond } = await apolloClient.query({ - query, - variables: { ...variables, offset: variables.offset + halfLimit, limit: variables.limit - halfLimit }, - }); - console.log('second half success'); - data = { - accounts: { - nodes: [...dataFirst.accounts.nodes, ...dataSecond.accounts.nodes], - totalCount: dataFirst.accounts.totalCount, - limit: dataFirst.accounts.limit + dataSecond.accounts.limit, - offset: dataFirst.accounts.offset, - }, - }; - } - - if (data) { - break; - } + const { data } = await apolloClient.query({ query, variables }); + return data; } catch (error) { - console.error('Error while fetching data', error); + console.error(`Attempt ${attempt}/${maxRetries} failed:`, error.message); + if (attempt < maxRetries) { + console.log(`Retrying...`); + } } } - if (!data) { - throw new Error('Failed to fetch data'); - } - - return data; + throw new Error('Failed to fetch data after multiple retries'); } async function fetchDataForPage(host) { - const { slug, currency, root } = host; + const { slug, hostSlugs, currency } = host; const quarterFrom = dayjs.utc().subtract(12, 'week').startOf('isoWeek').toISOString(); const quarterTo = dayjs.utc().subtract(1, 'week').endOf('isoWeek').toISOString(); const yearFrom = dayjs.utc().subtract(12, 'month').startOf('month').toISOString(); const yearTo = dayjs.utc().subtract(1, 'month').endOf('month').toISOString(); const variables = { - ...(root ? { host: host.hostSlugs.map(slug => ({ slug })) } : { host: { slug } }), + host: hostSlugs ? hostSlugs.map(s => ({ slug: s })) : { slug }, currency, quarterFrom, quarterTo, yearFrom, yearTo, offset: 0, - limit: 250, + limit: 100, }; + console.log(variables); + let data = await graphqlRequest(accountsQuery, variables); if (data.accounts.totalCount > data.accounts.limit) { From 571b86a13a891cd2cc8678e10a3490a08b24fb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Tue, 27 Jan 2026 12:11:08 +0100 Subject: [PATCH 4/9] move graphql files out of prettier --- .prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.prettierignore b/.prettierignore index 92c93fb..33ced6f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ public/* .next/* +lib/graphql/types/v2/* From c77d7c1aec3c94ba58a075093e27f6b9daa80746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Tue, 27 Jan 2026 17:11:45 +0100 Subject: [PATCH 5/9] add more collectives and review data loading --- lib/additional-collectives.ts | 624 ++++++++++++++++++++++++++++++++++ lib/graphql/queries.ts | 101 ++++++ package-lock.json | 301 ++++++---------- package.json | 1 + pages/[slug].tsx | 2 +- scripts/fetch-data.ts | 241 ++++++++++--- 6 files changed, 1037 insertions(+), 233 deletions(-) create mode 100644 lib/additional-collectives.ts diff --git a/lib/additional-collectives.ts b/lib/additional-collectives.ts new file mode 100644 index 0000000..a569a29 --- /dev/null +++ b/lib/additional-collectives.ts @@ -0,0 +1,624 @@ +/** + * List of collective slugs to include for their past activity, + * even though they are not currently hosted by one of the selected hosts. + */ +export const additionalCollectiveSlugs = [ + '1999collective', + '1k-casey', + '1kproject', + 'abqciqlovia', + 'abqfreefridge', + 'account-805de72b', + 'acpc', + 'act-fund', + 'adlt', + 'adviseccr', + 'afchildhoodjoyfund', + 'affect', + 'alaskansocial', + 'amazmod-33', + 'amazon-workers-for-democracy', + 'amiberry', + 'ampalibe', + 'androidide', + 'android-password-store', + 'angular-fullstack', + 'aosus', + 'apexchartsjs', + 'apmutualaid', + 'appalachian-land-study', + 'arataki-impact-fibre-isp', + 'archived-b5wteifahzy01', + 'archived-collective-984651', + 'archived-collective-info', + 'art_coop', + 'artistideal', + 'artistledgivingcircle', + 'asd-dads', + 'asianfoodcollective', + 'aspiration-project-disbursement', + 'athens', + 'athensmutualaidnetwork', + 'atx-mental-health-fund2', + 'autismandmeguernsey', + 'awp', + 'aylx', + 'baltimore-code-and-coffee', + 'baltimoremusicalimprov', + 'beacon-community-network', + 'beaker', + 'bedrock', + 'beehaw', + 'behind-enemy-lines', + 'benthos', + 'bettercivics', + 'betterdisplay', + 'betweenthewires', + 'bftp', + 'big-arts-organization', + 'bikebike-everywhere', + 'bismuth', + 'bkhroc', + 'black-families-love-and-unite', + 'blacklivesmatteratschool', + 'black-mothers-march', + 'black-trans-collective', + 'boostnoteio', + 'breath', + 'brent-solidarity-fund', + 'brew', + 'bronzeville-kenwood-ma', + 'bsz', + 'bull-city-mutual-aid', + 'bushwick-emergency-relief-fund', + 'buttercup', + 'cachet', + 'caesonia', + 'cannercms', + 'cann-hall-mutual-aid', + 'canonbury-mutual-aid-2020', + 'cant-buy-my-silence-us', + 'carbon-app', + 'cares-fox-cities', + 'carleton-college', + 'carlislechp', + 'carols-kindness', + 'ccc33chicago', + 'cfsc', + 'change-waco', + 'charmcityjs', + 'chatwoot', + 'cheeselab', + 'chehalisrivermutualaidnetwork', + 'chicagolandwarehousefund', + 'chicago-ma-build-squad', + 'chicago-tenants-movement', + 'chordata', + 'chrome-vue', + 'chsf', + 'circle-city-mutual-aid', + 'civic-tech-dc', + 'claudia-jones-school', + 'cleveland-books-to-prisoners', + 'climate-collective-oxford', + 'climate-hub-wandsworth', + 'climatewborders', + 'cloudbox', + 'clubber-ml', + 'cmag', + 'cmeg-deai-fund', + 'code-and-coffee', + 'codeblue', + 'codebuddies', + 'code-for-dayton', + 'codefordenver', + 'code-for-hawaii-back', + 'code-for-pittsburgh', + 'code-with-aloha', + 'code-with-the-carolinas', + 'collectivediaspora', + 'columbiana-county-pride', + 'commonplace', + 'communityco-housing', + 'community-fruit', + 'communityrule', + 'comrade-co-op', + 'convoy4ukraine', + 'coop4lib', + 'cooperativejournal', + 'cotowali', + 'council-data-project', + 'covid-print-oxford', + 'cpstrong', + 'crop-la', + 'csh-food-co-op', + 'cupcake', + 'cursorless', + 'cypurr-collective', + 'data-empowerment-fund', + 'dbt-athena', + 'dc-mutual-aid-apothecary', + 'dc-tool-library', + 'decoupled-days', + 'denvercommunityfridges', + 'denverscript', + 'dep', + 'dev-founders', + 'disco-one-project-grant', + 'distribute-aid-usa', + 'distributed', + 'dividon', + 'dj-stripe', + 'do-collective-art-projects', + 'donoharmatvcu', + 'donut', + 'drive-by-howdy', + 'drupalcamp-bristol', + 'drupal-diversity-and-inclusion-speaker-initiative', + 'drupalnj', + 'drupalstl', + 'drupalutah', + 'dsmsurj', + 'dual-power-gathering', + 'duopo', + 'durham-community-apothecary', + 'eastlaketenants', + 'econ', + 'edgewater-mutual-aid', + 'eitherorg', + 'elgin-mutual-aid-collective', + 'elmsln', + 'emdr-london-group', + 'emplea_do', + 'encampment-medical-team', + 'endurance-scholarship', + 'entropic', + 'erutan-art', + 'esa-ne', + 'evanston-fight-for-black-lives', + 'ewan-brown-anarchist-bookfair', + 'expo-cli', + 'expressots', + 'eyedropper', + 'f4mp', + 'fair-collective', + 'farstar', + 'favor', + 'feathers', + 'federatedpress', + 'fela', + 'femme-defensa', + 'ffplayout', + 'file-icons', + 'fines-and-fees-freedom-fund', + 'fisk-vanderbilt-masters-to-phd-bridge-program', + 'fito-network', + 'fits', + 'flatpak', + 'florence-social', + 'flourish-films', + 'flowlab', + 'fnb_raleigh', + 'focused-collaboration', + 'forma-institut', + 'fountainjs', + 'fpvout', + 'freebsd-vm-bhyve', + 'freefoodcollective', + 'fridays-for-future-dc', + 'fridays-for-future-nyc', + 'fridaysforfuture-us', + 'fridaysforfuture-usd-it-admin', + 'friends-of-green-burial-pa', + 'friends-of-the-howard-human-and-civil-rights-law-review', + 'fxma', + 'gatsby', + 'gatsby-plugin-typegen', + 'getbootstrapcdn', + 'geysermcold', + 'gitlens', + 'glimpse', + 'godscloset', + 'google-open-source-cms-fund', + 'goose-green-solidarity-fund', + 'gqless', + 'graphqlentityframework', + 'graphqlnyc', + 'guardian-rebellion', + 'hack4impact', + 'handsoffuhuru', + 'hava-nababy', + 'heaux-history', + 'heklerke', + 'help-escaping-afghanistan', + 'henleycmag', + 'hermosamutualaid', + 'hernehillsolidarityfund', + 'hibiscusrose', + 'home-is-a-human-right', + 'hospitalrun', + 'hots-nyc', + 'housingmv', + 'hpsolidaritynetwork', + 'htop', + 'humanitarian-ai', + 'huneeds', + 'hyperapp', + 'ideation-youth-nation', + 'idyll', + 'ievobio', + 'ifcjs', + 'ihateregex', + 'impactfellowship', + 'impacttech', + 'inflection-grants', + 'invoiceplane', + 'inw-mutual-aid', + 'inxi', + 'ipfs-search', + 'iron-path-farms', + 'irving-park-mutual-aid', + 'ispcwa_old', + 'jesuisniilee-ma', + 'joinedincare', + 'jordans-safe-place', + 'jovo-framework', + 'jsbin', + 'julian-collective', + 'justice-reskill', + 'karafka', + 'kcmutualaidzone3', + 'keeling-mutual-aid', + 'kefc', + 'killedbygoogle', + 'kintsugiproject', + 'kolanutcollab', + 'kuumba-collective', + 'labdavanac', + 'lan4n', + 'landcorps', + 'laugh-it-out-hub', + 'launchcoop', + 'lefrak-corona-ma', + 'leveler', + 'leyton-ward-mutual-aid', + 'libra', + 'lindberg-park-neighborhood-association', + 'livinginquiries', + 'ljescuelita', + 'local-peace-economy', + 'logansquaremutualaid', + 'lolashare', + 'lollipop-cloud-team', + 'los-angeles-global-shapers', + 'lovewins', + 'loving', + 'lumen', + 'lumo', + 'lvlpma', + 'm4a-everywhere', + 'm4bl-dc-money-pot', + 'mahappsmetro', + 'mainlinezine', + 'mamas', + 'manifest-grants', + 'manybabies', + 'mapletestimony', + 'material-theme', + 'ma-vie-en-mode-avion', + 'mayfair-mutual-aid', + 'mealsofgratitude', + 'mealstohealoc', + 'memexgarden', + 'metagov-phase-3', + 'mhisa-fundraise', + 'michigan-drupal', + 'missingpiece', + 'ml4chemla', + 'mobile-ffmpeg', + 'molembike', + 'money-health-collective', + 'moneyjustice', + 'mozilla', + 'mp-collective', + 'mqttnet', + 'muntashir', + 'museum-workers-speak', + 'mutual-aid-alnwick', + 'native-roots-network', + 'nature-connection-network', + 'nebula', + 'newschoolmutualaidfund', + 'newtonsoftjson-for-unity', + 'ng-packagr', + 'nmhep', + 'noble', + 'nobo-house', + 'nodemailer', + 'node-redis', + 'nokomiseastmutualaid', + 'nomuslimban', + 'nopa-neighbors', + 'north-brooklyn-mutual-aid', + 'nssc', + 'nunhead-knocks', + 'nwma-coalition-chicago', + 'nycpp', + 'nytmma', + 'obvious-agency', + 'occ-ai', + 'oc-covid-relief-fund', + 'ocf-temporary-fund', + 'octotree', + 'offthestage', + 'onetwoheartu', + 'opencleveland', + 'open-eugene', + 'openipc-eu', + 'open-kentuckiana', + 'openknx-collective', + 'openmaine', + 'openmined', + 'opennashville', + 'openoakland', + 'openpatterson', + 'open-post-academics', + 'openprivacytech', + 'openproducerplatform', + 'opensanctions', + 'opensandiego', + 'openscapes', + 'opensourcediversity', + 'open-source-san-jose', + 'open-streaming-platform', + 'open-twin-cities', + 'openulmus', + 'orchidssg', + 'orogene', + 'ory', + 'ospo', + 'ourhouseinfoshop', + 'outkast-people-nation', + 'palestineaction', + 'palms-unhoused-mutual-aid', + 'pamf-hospital-meals', + 'pasquines', + 'patchbay', + 'patchwork', + 'paths-chicago', + 'pcp', + 'pdxnode', + 'peercoin', + 'peertubesocial', + 'peoples-utility', + 'pgchangemakers', + 'phenomic', + 'phillytru', + 'phoenix-hub', + 'phpstanorg', + 'piamancini-collective', + 'picocrypt', + 'pinpointsimulations', + 'pion-ion', + 'pistil', + 'pnwzone', + 'polar-bookshelf', + 'police-free-penn-mutual-aid', + 'polyphonics-choir', + 'portland-corona-virus-mutual-aid-fund', + 'possibilities-for-women', + 'possibilityproject', + 'power-for-all', + 'pressbooks', + 'print-my-blog', + 'priorswood', + 'privacyguides', + 'project-acp', + 'project-protocol', + 'project-safe-philly', + 'promenaidorg', + 'ps376psa', + 'psysciacc', + 'pug-php', + 'pwa', + 'python-software-foundation', + 'python-sortedcontainers', + 'qc-bytes', + 'qrcodeshow', + 'qrpicture', + 'quantum', + 'quarry-community-mutual-aid', + 'queering-the-path', + 'quodlibet', + 'r4ds', + 'ragtag', + 'railsgirlsatl', + 'rain-or-shine-diaper-fund', + 'raw', + 'rbkcarea2mutualaid', + 'react-hot-loader', + 'react-native-camera', + 'react-robins', + 'react-starter', + 'readup-collective', + 'redact', + 'redom', + 'redredemption', + 'refugee-resettlement-support', + 'regenerationcentre', + 're-opening-welcome-hall', + 'repro-justice-in-adoption', + 'residency-at-papillon-farm', + 'rethinking-economics-usa', + 'retrospring', + 'revery', + 'ridgewood-mutual-aid-project', + 'rise-and-shine', + 'rocketmod', + 'rockymountainmutualaid', + 'roidelapluie', + 'rometoolsbackup', + 'rootsunbound', + 'rootz', + 'roseto', + 'roslindale-community-fridge', + 'roslindale-food-collective', + 'rs-ipfs', + 'rumah', + 'runa-ctx', + 'rwf2', + 'sage', + 'satellizer', + 'sb-mutual-aid-care-club', + 'se16communityfund', + 'searx', + 'seattleliteracy', + 'secure-activism', + 'see-you-collective', + 'selborne-mutual-aid', + 'serpent-os', + 'sfba-mastodon', + 'sf-bay-area-mutual-aid', + 'sfcontemplarium', + 'sf-data-science', + 'sfglobalshapers', + 'short-story-club', + 'silhouette', + 'sinuous', + 'skatejs', + 'sketch-sh', + 'skgreactjs', + 'skylines', + 'smcsalumni', + 'socialhealthlabs', + 'sociallydistantart', + 'solarpunk', + 'solidaridad-inquilina', + 'solrmutualaid', + 'southeasttnmutualaidnetwork', + 'southphillyfridge', + 'sovereign-bodies-institute', + 'speak-brussels', + 'spring-garden-community-pantry', + 'ssftx', + 'ssma', + 'stanford-hospital-meals', + 'staticjscms', + 'statusfy', + 'stc', + 'stlmutualaid', + 'stoke-newington-c19-mutual-aid', + 'stop-the-sweeps-atx', + 'streamlink', + 'suedwestnetz', + 'sunflower', + 'sunrise-choir', + 'sun-seeker-mke', + 'superbloom-samsung-next-decentralization-grant', + 'supplyourheroes', + 'sustain-general-support-grant', + 'swop-chi', + 'symfony-diversity-speaker-mentoring', + 'tallahassee-food-not-bombs', + 'tania', + 'tavistocklocalshelp', + 'tavistock-scrubs-hub', + 'tb-deprecated', + 'tech-for-forest', + 'techtogether-atlanta', + 'techtogether-boston', + 'techtogethermiami', + 'techtogether-new-york', + 'techtogether-seattle', + 'temple2023', + 'ten-the-emergence-network', + 'tera-arise', + 'te-rourou-koha', + 'testing-library', + 'the-archive-project', + 'the-black-and-brown-collective', + 'the-camila-foundation', + 'the-eleventy-meetup', + 'the-future-is-us-collective', + 'the-get-to-college-project', + 'thegradient', + 'the-headband-project', + 'the-love-fridge', + 'themis', + 'themovingsupportproject', + 'the-persistence', + 'thereservoircollective', + 'thesgc', + 'the-training-collective-nyc', + 'the-unjournal', + 'thewceg', + 'the-week', + 'thexylom', + 'togglz', + 'tpf', + 'tpsda', + 'trails', + 'training-europe', + 'trellis', + 'trill', + 'trilogymp', + 'truecharts-bounties', + 'trust-fund', + 'ttww-infrastructure-fund', + 'turkey-and-syria-response-fund', + 'turn-down-for-what-nyc', + 'twisted-fields-research-collective', + 'txdug', + 'ukvma', + 'umd', + 'undoingourerasure', + 'unirx', + 'untitled-filmmaker-org', + 'uob-mutual-support', + 'upliftinkind', + 'upstatephp', + 'urbanistssocial', + 'usrse', + 'utahmutualaid', + 'uv', + 'vaccinehunter', + 'vacfind', + 'vertical-village-alliance', + 'vicious-mole', + 'volteuropa', + 'vuejsfrankfurt', + 'vue-native-core', + 'wallrunners', + 'walthamstow-chapel-end', + 'ward-2-mutual-aid', + 'ward3neighborsupport', + 'ward-5-mutual-aid', + 'warriorjs', + 'wa-state-3d-visor-mask-hub', + 'watchersdefensecollective', + 'wccat', + 'weberfridge', + 'we-care-for-us', + 'wenyan-lang', + 'westbrooklynwaterfrontmutualaid', + 'west-side-mutual-aid', + 'whakapono-believe', + 'whanau-ahirau', + 'windicss', + 'worcestercommunityfridges', + 'wordpress-gwinnett', + 'worldimprovementlab', + 'wpbmutualaid', + 'wp-discourse', + 'wv-democracy-fund', + 'wwwdxmarathoncom', + 'wwwmanhoodpagcouk', + 'xplr', + 'xr-namur', + 'yfcrm', + 'yoga-para-la-gente', + 'york-community-energy', + 'youngstownactioncenter', + 'zane-access-co', + 'zipstream', +]; diff --git a/lib/graphql/queries.ts b/lib/graphql/queries.ts index a010034..0ffc6cf 100644 --- a/lib/graphql/queries.ts +++ b/lib/graphql/queries.ts @@ -115,3 +115,104 @@ export const totalCountQuery = gql` } } `; + +export const accountBySlugQuery = gql` + query AccountBySlug( + $slug: String! + $quarterFrom: DateTime + $quarterTo: DateTime + $yearFrom: DateTime + $yearTo: DateTime + $currency: Currency + ) { + account(slug: $slug, throwIfMissing: false) { + name + slug + imageUrl + tags + location { + country + } + ... on AccountWithHost { + host { + slug + name + } + } + + ALL: stats { + contributorsCount(includeChildren: true) + totalAmountSpent(net: true, includeChildren: true, currency: $currency) { + valueInCents + } + totalAmountReceivedTimeSeries(net: true, timeUnit: YEAR, includeChildren: true, currency: $currency) { + timeUnit + nodes { + date + amount { + valueInCents + } + } + } + } + + PAST_YEAR: stats { + contributorsCount(includeChildren: true, dateFrom: $yearFrom, dateTo: $yearTo) + totalAmountSpent( + net: true + includeChildren: true + dateFrom: $yearFrom + dateTo: $yearTo + currency: $currency + ) { + valueInCents + } + totalAmountReceivedTimeSeries( + net: true + dateFrom: $yearFrom + dateTo: $yearTo + timeUnit: MONTH + includeChildren: true + currency: $currency + ) { + timeUnit + nodes { + date + amount { + valueInCents + } + } + } + } + + PAST_QUARTER: stats { + contributorsCount(includeChildren: true, dateFrom: $quarterFrom, dateTo: $quarterTo) + totalAmountSpent( + net: true + includeChildren: true + dateFrom: $quarterFrom + dateTo: $quarterTo + currency: $currency + ) { + valueInCents + } + totalAmountReceivedTimeSeries( + net: true + dateFrom: $quarterFrom + dateTo: $quarterTo + timeUnit: WEEK + includeChildren: true + currency: $currency + ) { + timeUnit + nodes { + date + amount { + valueInCents + } + } + } + } + } + } +`; diff --git a/package-lock.json b/package-lock.json index 41320b0..a9c6b0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "babel-plugin-formatjs": "^10.3.31", "babel-plugin-react-remove-properties": "^0.3.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", + "bottleneck": "^2.19.5", "class-variance-authority": "^0.3.0", "dayjs": "^1.11.6", "deepmerge": "4.2.2", @@ -384,6 +385,7 @@ "version": "7.19.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -2844,7 +2846,6 @@ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", "dev": true, - "peer": true, "dependencies": { "eslint-scope": "5.1.1" } @@ -4970,7 +4971,8 @@ "node_modules/@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "peer": true }, "node_modules/@types/node-fetch": { "version": "2.6.2", @@ -5007,6 +5009,7 @@ "version": "18.0.21", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.21.tgz", "integrity": "sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==", + "peer": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -5119,6 +5122,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.40.1.tgz", "integrity": "sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.40.1", "@typescript-eslint/types": "5.40.1", @@ -5387,6 +5391,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5514,7 +5519,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -5539,6 +5543,7 @@ "version": "3.36.3", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.36.3.tgz", "integrity": "sha512-8/FXEs0ohXMff07Gv28XjhPwEJphIUdq2/wii/pcvi54Tw6z1mjrV8ydN8rlWi/ve8BAPBefJkLmRWv7UOBsLw==", + "peer": true, "dependencies": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -5929,6 +5934,12 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5965,6 +5976,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -6143,7 +6155,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -6372,7 +6383,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, "dependencies": { "color-name": "1.1.3" } @@ -6381,8 +6391,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/colorette": { "version": "2.0.19", @@ -6483,6 +6492,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "peer": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -7069,6 +7079,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz", "integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.3.3", "@humanwhocodes/config-array": "^0.10.5", @@ -7230,6 +7241,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", "dev": true, + "peer": true, "dependencies": { "eslint-rule-composer": "^0.3.0" }, @@ -7320,6 +7332,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -7374,6 +7387,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", "dev": true, + "peer": true, "dependencies": { "@babel/runtime": "^7.18.9", "aria-query": "^4.2.2", @@ -7407,6 +7421,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "peer": true, "dependencies": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -7451,6 +7466,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.5", "array.prototype.flatmap": "^1.3.0", @@ -7540,7 +7556,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true, - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7553,7 +7568,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7563,7 +7577,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7573,7 +7586,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -7583,7 +7595,6 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", "dev": true, - "peer": true, "dependencies": { "restore-cursor": "^2.0.0" }, @@ -7595,15 +7606,13 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/eslint-plugin-styled-components-a11y/node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, - "peer": true, "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -7709,7 +7718,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, - "peer": true, "dependencies": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" @@ -7723,7 +7731,6 @@ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, - "peer": true, "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -7736,7 +7743,6 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7746,7 +7752,6 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", "dev": true, - "peer": true, "dependencies": { "acorn": "^6.0.7", "acorn-jsx": "^5.0.0", @@ -7761,7 +7766,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "peer": true, "engines": { "node": ">=4.0" } @@ -7771,7 +7775,6 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, - "peer": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -7784,7 +7787,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, - "peer": true, "dependencies": { "flat-cache": "^2.0.1" }, @@ -7797,7 +7799,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, - "peer": true, "dependencies": { "flatted": "^2.0.0", "rimraf": "2.6.3", @@ -7811,15 +7812,13 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/eslint-plugin-styled-components-a11y/node_modules/ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true, - "peer": true, "engines": { "node": ">= 4" } @@ -7829,7 +7828,6 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, - "peer": true, "dependencies": { "ansi-escapes": "^3.2.0", "chalk": "^2.4.2", @@ -7854,7 +7852,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -7864,7 +7861,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^4.1.0" }, @@ -7877,7 +7873,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7887,7 +7882,6 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7901,7 +7895,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -7915,7 +7908,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7925,7 +7917,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -7937,15 +7928,13 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/eslint-plugin-styled-components-a11y/node_modules/onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", "dev": true, - "peer": true, "dependencies": { "mimic-fn": "^1.0.0" }, @@ -7958,7 +7947,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, - "peer": true, "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -7976,7 +7964,6 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -7986,7 +7973,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -7996,7 +7982,6 @@ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true, - "peer": true, "engines": { "node": ">=6.5.0" } @@ -8006,7 +7991,6 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", "dev": true, - "peer": true, "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -8020,7 +8004,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -8033,7 +8016,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, - "peer": true, "dependencies": { "tslib": "^1.9.0" }, @@ -8046,7 +8028,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver" } @@ -8056,7 +8037,6 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, - "peer": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -8069,7 +8049,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8079,7 +8058,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, - "peer": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -8093,7 +8071,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^3.0.0" }, @@ -8106,7 +8083,6 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8115,15 +8091,13 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/eslint-plugin-styled-components-a11y/node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, - "peer": true, "dependencies": { "prelude-ls": "~1.1.2" }, @@ -8136,7 +8110,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -8770,8 +8743,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "peer": true + "dev": true }, "node_modules/functions-have-names": { "version": "1.2.3", @@ -8920,6 +8892,7 @@ "version": "16.6.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "peer": true, "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } @@ -9024,6 +8997,7 @@ "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.11.2.tgz", "integrity": "sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w==", "devOptional": true, + "peer": true, "engines": { "node": ">=10" }, @@ -11162,6 +11136,7 @@ "version": "12.3.1", "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "peer": true, "dependencies": { "@next/env": "12.3.1", "@swc/helpers": "0.4.11", @@ -11300,8 +11275,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/no-case": { "version": "3.0.4", @@ -11362,16 +11336,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, - "node_modules/nodemailer": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", - "integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==", - "optional": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -11849,8 +11813,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/path-key": { "version": "3.1.1", @@ -11952,6 +11915,7 @@ "url": "https://tidelift.com/funding/github/npm/postcss" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -12067,6 +12031,7 @@ "version": "10.11.2", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.2.tgz", "integrity": "sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -12097,6 +12062,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "dev": true, + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -12129,7 +12095,6 @@ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, - "peer": true, "engines": { "node": ">=0.4.0" } @@ -12238,6 +12203,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -12277,6 +12243,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -12369,8 +12336,7 @@ "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "peer": true + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-lite-youtube-embed": { "version": "2.3.52", @@ -13349,6 +13315,7 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", @@ -13402,6 +13369,7 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", + "peer": true, "dependencies": { "@styled-system/background": "^5.1.2", "@styled-system/border": "^5.1.5", @@ -13574,7 +13542,6 @@ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, - "peer": true, "dependencies": { "ajv": "^6.10.2", "lodash": "^4.17.14", @@ -13590,7 +13557,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -13600,7 +13566,6 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -13609,15 +13574,13 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "peer": true + "dev": true }, "node_modules/table/node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, - "peer": true, "engines": { "node": ">=4" } @@ -13627,7 +13590,6 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", @@ -13642,7 +13604,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, - "peer": true, "dependencies": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -13657,7 +13618,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^4.1.0" }, @@ -13856,6 +13816,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -13972,6 +13933,7 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14550,7 +14512,6 @@ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, - "peer": true, "dependencies": { "mkdirp": "^0.5.1" }, @@ -14563,7 +14524,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -14576,6 +14536,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "dev": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -14936,6 +14897,7 @@ "version": "7.19.6", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz", "integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==", + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -16742,7 +16704,6 @@ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", "dev": true, - "peer": true, "requires": { "eslint-scope": "5.1.1" } @@ -18057,7 +18018,8 @@ "@types/node": { "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "peer": true }, "@types/node-fetch": { "version": "2.6.2", @@ -18094,6 +18056,7 @@ "version": "18.0.21", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.21.tgz", "integrity": "sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==", + "peer": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -18183,6 +18146,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.40.1.tgz", "integrity": "sha512-IK6x55va5w4YvXd4b3VrXQPldV9vQTxi5ov+g4pMANsXPTXOcfjx08CRR1Dfrcc51syPtXHF5bgLlMHYFrvQtg==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "5.40.1", "@typescript-eslint/types": "5.40.1", @@ -18351,7 +18315,8 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -18442,7 +18407,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, "requires": { "color-convert": "^1.9.0" } @@ -18461,6 +18425,7 @@ "version": "3.36.3", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.36.3.tgz", "integrity": "sha512-8/FXEs0ohXMff07Gv28XjhPwEJphIUdq2/wii/pcvi54Tw6z1mjrV8ydN8rlWi/ve8BAPBefJkLmRWv7UOBsLw==", + "peer": true, "requires": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -18753,6 +18718,11 @@ "readable-stream": "^3.4.0" } }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -18776,6 +18746,7 @@ "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", + "peer": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -18892,7 +18863,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -19054,7 +19024,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, "requires": { "color-name": "1.1.3" } @@ -19063,8 +19032,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true + "dev": true }, "colorette": { "version": "2.0.19", @@ -19140,6 +19108,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "peer": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -19589,6 +19558,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz", "integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^1.3.3", "@humanwhocodes/config-array": "^0.10.5", @@ -19810,6 +19780,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", "dev": true, + "peer": true, "requires": { "eslint-rule-composer": "^0.3.0" } @@ -19872,6 +19843,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.4", "array.prototype.flat": "^1.2.5", @@ -19919,6 +19891,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", "dev": true, + "peer": true, "requires": { "@babel/runtime": "^7.18.9", "aria-query": "^4.2.2", @@ -19948,6 +19921,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, + "peer": true, "requires": { "eslint-plugin-es": "^3.0.0", "eslint-utils": "^2.0.0", @@ -19979,6 +19953,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", "dev": true, + "peer": true, "requires": { "array-includes": "^3.1.5", "array.prototype.flatmap": "^1.3.0", @@ -20047,29 +20022,25 @@ "version": "6.4.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "peer": true + "dev": true }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true, - "peer": true + "dev": true }, "ansi-regex": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true + "dev": true }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, "requires": { "sprintf-js": "~1.0.2" } @@ -20079,7 +20050,6 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", "dev": true, - "peer": true, "requires": { "restore-cursor": "^2.0.0" } @@ -20088,15 +20058,13 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true, - "peer": true + "dev": true }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, - "peer": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -20177,7 +20145,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, - "peer": true, "requires": { "esrecurse": "^4.1.0", "estraverse": "^4.1.1" @@ -20188,7 +20155,6 @@ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, - "peer": true, "requires": { "eslint-visitor-keys": "^1.1.0" } @@ -20197,15 +20163,13 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "peer": true + "dev": true }, "espree": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", "dev": true, - "peer": true, "requires": { "acorn": "^6.0.7", "acorn-jsx": "^5.0.0", @@ -20216,15 +20180,13 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true + "dev": true }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", "dev": true, - "peer": true, "requires": { "escape-string-regexp": "^1.0.5" } @@ -20234,7 +20196,6 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, - "peer": true, "requires": { "flat-cache": "^2.0.1" } @@ -20244,7 +20205,6 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, - "peer": true, "requires": { "flatted": "^2.0.0", "rimraf": "2.6.3", @@ -20255,22 +20215,19 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true, - "peer": true + "dev": true }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "peer": true + "dev": true }, "inquirer": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, - "peer": true, "requires": { "ansi-escapes": "^3.2.0", "chalk": "^2.4.2", @@ -20291,15 +20248,13 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true + "dev": true }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "peer": true, "requires": { "ansi-regex": "^4.1.0" } @@ -20310,15 +20265,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true + "dev": true }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "peer": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -20329,7 +20282,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, - "peer": true, "requires": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -20339,15 +20291,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "peer": true + "dev": true }, "mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "peer": true, "requires": { "minimist": "^1.2.6" } @@ -20356,15 +20306,13 @@ "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", - "dev": true, - "peer": true + "dev": true }, "onetime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", "dev": true, - "peer": true, "requires": { "mimic-fn": "^1.0.0" } @@ -20374,7 +20322,6 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, - "peer": true, "requires": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -20388,29 +20335,25 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "peer": true + "dev": true }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "peer": true + "dev": true }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true, - "peer": true + "dev": true }, "restore-cursor": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", "dev": true, - "peer": true, "requires": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" @@ -20421,7 +20364,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, - "peer": true, "requires": { "glob": "^7.1.3" } @@ -20431,7 +20373,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, - "peer": true, "requires": { "tslib": "^1.9.0" } @@ -20440,15 +20381,13 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "peer": true + "dev": true }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, - "peer": true, "requires": { "shebang-regex": "^1.0.0" } @@ -20457,15 +20396,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "peer": true + "dev": true }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, - "peer": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -20476,7 +20413,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, - "peer": true, "requires": { "ansi-regex": "^3.0.0" } @@ -20485,22 +20421,19 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "peer": true + "dev": true }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "peer": true + "dev": true }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, - "peer": true, "requires": { "prelude-ls": "~1.1.2" } @@ -20510,7 +20443,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "peer": true, "requires": { "isexe": "^2.0.0" } @@ -20876,8 +20808,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true, - "peer": true + "dev": true }, "functions-have-names": { "version": "1.2.3", @@ -20984,7 +20915,8 @@ "graphql": { "version": "16.6.0", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", - "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "peer": true }, "graphql-config": { "version": "4.3.6", @@ -21060,6 +20992,7 @@ "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.11.2.tgz", "integrity": "sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w==", "devOptional": true, + "peer": true, "requires": {} }, "gray-matter": { @@ -22533,6 +22466,7 @@ "version": "12.3.1", "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "peer": true, "requires": { "@next/env": "12.3.1", "@next/swc-android-arm-eabi": "12.3.1", @@ -22613,8 +22547,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true, - "peer": true + "dev": true }, "no-case": { "version": "3.0.4", @@ -22651,13 +22584,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==" }, - "nodemailer": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.8.0.tgz", - "integrity": "sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==", - "optional": true, - "peer": true - }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -23008,8 +22934,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "dev": true, - "peer": true + "dev": true }, "path-key": { "version": "3.1.1", @@ -23075,6 +23000,7 @@ "version": "8.4.19", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "peer": true, "requires": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -23138,7 +23064,8 @@ "preact": { "version": "10.11.2", "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.2.tgz", - "integrity": "sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==" + "integrity": "sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==", + "peer": true }, "preact-render-to-string": { "version": "5.2.5", @@ -23158,7 +23085,8 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true + "dev": true, + "peer": true }, "prettier-plugin-tailwindcss": { "version": "0.1.13", @@ -23176,8 +23104,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "peer": true + "dev": true }, "promise": { "version": "7.3.1", @@ -23254,6 +23181,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -23278,6 +23206,7 @@ "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" @@ -23352,8 +23281,7 @@ "react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "peer": true + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "react-lite-youtube-embed": { "version": "2.3.52", @@ -24053,6 +23981,7 @@ "version": "5.3.11", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "peer": true, "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/traverse": "^7.4.5", @@ -24083,6 +24012,7 @@ "version": "5.1.5", "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", + "peer": true, "requires": { "@styled-system/background": "^5.1.2", "@styled-system/border": "^5.1.5", @@ -24209,7 +24139,6 @@ "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, - "peer": true, "requires": { "ajv": "^6.10.2", "lodash": "^4.17.14", @@ -24221,36 +24150,31 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true + "dev": true }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "peer": true + "dev": true }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "peer": true + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true + "dev": true }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, - "peer": true, "requires": { "ansi-styles": "^3.2.0", "astral-regex": "^1.0.0", @@ -24262,7 +24186,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, - "peer": true, "requires": { "emoji-regex": "^7.0.1", "is-fullwidth-code-point": "^2.0.0", @@ -24274,7 +24197,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, - "peer": true, "requires": { "ansi-regex": "^4.1.0" } @@ -24432,6 +24354,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -24511,7 +24434,8 @@ "typescript": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "peer": true }, "ua-parser-js": { "version": "0.7.32", @@ -24910,7 +24834,6 @@ "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, - "peer": true, "requires": { "mkdirp": "^0.5.1" }, @@ -24920,7 +24843,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "peer": true, "requires": { "minimist": "^1.2.6" } @@ -24932,6 +24854,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz", "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==", "dev": true, + "peer": true, "requires": {} }, "xtend": { diff --git a/package.json b/package.json index 79e9c72..caae082 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "babel-plugin-formatjs": "^10.3.31", "babel-plugin-react-remove-properties": "^0.3.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", + "bottleneck": "^2.19.5", "class-variance-authority": "^0.3.0", "dayjs": "^1.11.6", "deepmerge": "4.2.2", diff --git a/pages/[slug].tsx b/pages/[slug].tsx index 3e7bdf6..b1b2ed6 100644 --- a/pages/[slug].tsx +++ b/pages/[slug].tsx @@ -36,7 +36,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { name: collective.name, slug: collective.slug, imageUrl: collective.imageUrl.replace('-staging', ''), - host: { name: collective.host.name, slug: collective.host.slug }, + host: collective.host ? { name: collective.host.name, slug: collective.host.slug } : null, tags: transformTags(collective), stats: collective.stats, ...(location && { location }), diff --git a/scripts/fetch-data.ts b/scripts/fetch-data.ts index 64eb564..74937ad 100644 --- a/scripts/fetch-data.ts +++ b/scripts/fetch-data.ts @@ -1,12 +1,14 @@ import fs from 'fs'; import path from 'path'; +import Bottleneck from 'bottleneck'; import dayjs from 'dayjs'; import dayjsPluginIsoWeek from 'dayjs/plugin/isoWeek'; import dayjsPluginUTC from 'dayjs/plugin/utc'; import dotenv from 'dotenv'; import nodeFetch from 'node-fetch'; +import { additionalCollectiveSlugs } from '../lib/additional-collectives'; import { hosts } from '../lib/hosts'; // Load environment @@ -19,7 +21,7 @@ for (const env of ['local', process.env.NODE_ENV || 'development']) { } import { initializeApollo } from '../lib/apollo-client'; -import { accountsQuery, totalCountQuery } from '../lib/graphql/queries'; +import { accountBySlugQuery, accountsQuery, totalCountQuery } from '../lib/graphql/queries'; import { getAllCollectiveStats } from '../utils/stats'; @@ -28,22 +30,59 @@ dayjs.extend(dayjsPluginIsoWeek); const apolloClient = initializeApollo({ fetch: nodeFetch }); -async function graphqlRequest(query, variables: any = {}) { - const maxRetries = 5; +const hasValidStats = account => + account.ALL?.totalAmountReceivedTimeSeries && + account.PAST_YEAR?.totalAmountReceivedTimeSeries && + account.PAST_QUARTER?.totalAmountReceivedTimeSeries; - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - const { data } = await apolloClient.query({ query, variables }); - return data; - } catch (error) { - console.error(`Attempt ${attempt}/${maxRetries} failed:`, error.message); - if (attempt < maxRetries) { - console.log(`Retrying...`); +// Rate limiter: 60 requests per minute +const limiter = new Bottleneck({ + reservoir: 60, + reservoirRefreshAmount: 60, + reservoirRefreshInterval: 60 * 1000, // 1 minute + maxConcurrent: 5, +}); + +async function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function graphqlRequest(query, variables: any = {}): Promise<{ data: any; elapsed: string }> { + const requestId = variables.slug || variables.offset || 'unknown'; + return limiter.schedule(async () => { + const maxRetries = 5; + const requestStart = Date.now(); + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + const attemptStart = Date.now(); + try { + const { data } = await apolloClient.query({ query, variables }); + const elapsed = ((Date.now() - requestStart) / 1000).toFixed(2); + return { data, elapsed }; + } catch (error) { + const elapsed = ((Date.now() - attemptStart) / 1000).toFixed(2); + const is429 = error.message?.includes('429'); + const statusCode = error.networkError?.statusCode || error.statusCode; + const bodyText = error.networkError?.bodyText || error.bodyText; + console.error(`[${requestId}] Attempt ${attempt}/${maxRetries} failed after ${elapsed}s:`, error.message); + console.error(`[${requestId}] Variables:`, JSON.stringify(variables)); + if (statusCode) { + console.error(`[${requestId}] Status code: ${statusCode}`); + } + if (bodyText) { + console.error(`[${requestId}] Response body: ${bodyText.substring(0, 500)}`); + } + if (attempt < maxRetries) { + // Exponential backoff, longer for rate limit errors + const backoff = is429 ? Math.pow(2, attempt) * 1000 : attempt * 500; + console.log(`[${requestId}] Retrying in ${backoff}ms...`); + await sleep(backoff); + } } } - } - throw new Error('Failed to fetch data after multiple retries'); + throw new Error(`Failed to fetch data after multiple retries (${requestId})`); + }); } async function fetchDataForPage(host) { @@ -53,46 +92,124 @@ async function fetchDataForPage(host) { const yearFrom = dayjs.utc().subtract(12, 'month').startOf('month').toISOString(); const yearTo = dayjs.utc().subtract(1, 'month').endOf('month').toISOString(); - const variables = { + const pageSize = 10; + const baseVariables = { host: hostSlugs ? hostSlugs.map(s => ({ slug: s })) : { slug }, currency, quarterFrom, quarterTo, yearFrom, yearTo, - offset: 0, - limit: 100, }; - console.log(variables); + // First request to get total count + const { data: firstData } = await graphqlRequest(accountsQuery, { ...baseVariables, offset: 0, limit: pageSize }); + const totalCount = firstData.accounts.totalCount; + + if (totalCount <= pageSize) { + return firstData; + } + + // Calculate pages needed (excluding first page already fetched) + const remainingPages = Math.ceil((totalCount - pageSize) / pageSize); + const offsets = Array.from({ length: remainingPages }, (_, i) => (i + 1) * pageSize); + + console.log(`Fetching ${totalCount} accounts in ${remainingPages + 1} pages (page size: ${pageSize})`); + + let fetchedPages = 0; + const fetchPage = async (offset: number) => { + const { data, elapsed } = await graphqlRequest(accountsQuery, { ...baseVariables, offset, limit: pageSize }); + fetchedPages++; + console.log( + `Page ${fetchedPages}/${remainingPages}: offset ${offset}, fetched ${data.accounts.nodes.length} in ${elapsed}s`, + ); + return data.accounts.nodes; + }; + + // Fetch all remaining pages concurrently with rate limiting + const startTime = Date.now(); + const pageResults = await Promise.all(offsets.map(fetchPage)); + const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); - let data = await graphqlRequest(accountsQuery, variables); + // Merge all nodes + const allNodes = [firstData.accounts.nodes, ...pageResults].flat(); + console.log(`Total: fetched ${allNodes.length} accounts in ${elapsed}s`); - if (data.accounts.totalCount > data.accounts.limit) { - let nodes = [...data.accounts.nodes]; - do { - variables.offset += data.accounts.limit; - console.log(`Paginating with offset ${variables.offset}`); - const startTime = Date.now(); - data = await graphqlRequest(accountsQuery, variables); - const endTime = Date.now(); - console.log(`Fetched in ${(endTime - startTime) / 1000} s`); - nodes = [...nodes, ...data.accounts.nodes]; - } while (data.accounts.totalCount > data.accounts.limit + data.accounts.offset); + return { + accounts: { + ...firstData.accounts, + totalCount, + offset: 0, + limit: totalCount, + nodes: allNodes, + }, + }; +} - data = { - accounts: { - ...data.accounts, - offset: 0, - limit: data.accounts.totalCount, - nodes, - }, - }; - } +async function fetchAdditionalCollectives(currency: string, existingSlugs: Set) { + const quarterFrom = dayjs.utc().subtract(12, 'week').startOf('isoWeek').toISOString(); + const quarterTo = dayjs.utc().subtract(1, 'week').endOf('isoWeek').toISOString(); + const yearFrom = dayjs.utc().subtract(12, 'month').startOf('month').toISOString(); + const yearTo = dayjs.utc().subtract(1, 'month').endOf('month').toISOString(); + + const slugsToFetch = additionalCollectiveSlugs.filter(slug => !existingSlugs.has(slug)); + + console.log( + `Fetching ${slugsToFetch.length} additional collectives (${ + additionalCollectiveSlugs.length - slugsToFetch.length + } already in host data)`, + ); + + const fetchAccount = async (slug: string) => { + try { + const { data } = await graphqlRequest(accountBySlugQuery, { + slug, + currency, + quarterFrom, + quarterTo, + yearFrom, + yearTo, + }); + return data.account; + } catch (error) { + console.error(`Failed to fetch ${slug}:`, error.message); + return null; + } + }; + + // Progress tracking + let completed = 0; + const total = slugsToFetch.length; + const startTime = Date.now(); - if (data) { - return data; + const results = await Promise.all( + slugsToFetch.map(async slug => { + const result = await fetchAccount(slug); + completed++; + if (completed % 10 === 0 || completed === total) { + const elapsed = (Date.now() - startTime) / 1000; + console.log(`Progress: ${completed}/${total}`); + } + return result; + }), + ); + + // Filter out null results and accounts without proper stats structure + const collectives = []; + for (let i = 0; i < results.length; i++) { + const account = results[i]; + const slug = slugsToFetch[i]; + if (!account) { + console.warn(`Warning: Account not found: ${slug}`); + } else if (!hasValidStats(account)) { + console.warn(`Warning: Account has incomplete stats data: ${slug}`); + } else { + collectives.push(account); + } } + console.log(`Fetched ${collectives.length} valid collectives out of ${total} slugs`); + + return collectives; } async function run() { @@ -101,9 +218,7 @@ async function run() { data: { accounts: { totalCount }, }, - } = await apolloClient.query({ - query: totalCountQuery, - }); + } = await graphqlRequest(totalCountQuery); const directory = path.join(__dirname, '..', '_data'); @@ -140,6 +255,17 @@ async function run() { const updatedAccounts = { ...data.accounts, nodes: data.accounts.nodes + .filter(account => { + if (!hasValidStats(account)) { + console.warn( + `Warning: Account has incomplete stats data: ${ + account?.slug || account?.name || JSON.stringify(account) + }`, + ); + return false; + } + return true; + }) .map(account => { const stats = getAllCollectiveStats(account); return { @@ -156,6 +282,35 @@ async function run() { }; if (host.root) { + // Fetch additional collectives and merge them into root data + const existingSlugs = new Set(data.accounts.nodes.map(a => a.slug)); + console.log('Fetching additional collectives...'); + const additionalCollectives = await fetchAdditionalCollectives(host.currency, existingSlugs); + + // Process stats for additional collectives and filter those with no activity + const processedAdditional = []; + for (const account of additionalCollectives) { + const stats = getAllCollectiveStats(account); + if (stats) { + processedAdditional.push({ ...account, stats }); + } else { + console.warn(`Warning: Account has no activity: ${account.slug}`); + } + } + + console.log(`Adding ${processedAdditional.length} additional collectives with activity`); + + // Merge additional collectives into root data + data = { + ...data, + accounts: { + ...data.accounts, + totalCount: data.accounts.totalCount + processedAdditional.length, + limit: data.accounts.limit + processedAdditional.length, + nodes: [...data.accounts.nodes, ...processedAdditional], + }, + }; + rootData = data; } } From 4cb82780958122a1ae29c179feb1cc76b49af1b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Tue, 27 Jan 2026 17:35:26 +0100 Subject: [PATCH 6/9] lint and prettier --- lib/graphql/queries.ts | 8 +------- scripts/fetch-data.ts | 2 -- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/graphql/queries.ts b/lib/graphql/queries.ts index 0ffc6cf..08ba07a 100644 --- a/lib/graphql/queries.ts +++ b/lib/graphql/queries.ts @@ -158,13 +158,7 @@ export const accountBySlugQuery = gql` PAST_YEAR: stats { contributorsCount(includeChildren: true, dateFrom: $yearFrom, dateTo: $yearTo) - totalAmountSpent( - net: true - includeChildren: true - dateFrom: $yearFrom - dateTo: $yearTo - currency: $currency - ) { + totalAmountSpent(net: true, includeChildren: true, dateFrom: $yearFrom, dateTo: $yearTo, currency: $currency) { valueInCents } totalAmountReceivedTimeSeries( diff --git a/scripts/fetch-data.ts b/scripts/fetch-data.ts index 74937ad..153bfe1 100644 --- a/scripts/fetch-data.ts +++ b/scripts/fetch-data.ts @@ -180,14 +180,12 @@ async function fetchAdditionalCollectives(currency: string, existingSlugs: Set { const result = await fetchAccount(slug); completed++; if (completed % 10 === 0 || completed === total) { - const elapsed = (Date.now() - startTime) / 1000; console.log(`Progress: ${completed}/${total}`); } return result; From c75836b4e6c8482718aca0f43db647880c20f46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Tue, 27 Jan 2026 20:23:11 +0100 Subject: [PATCH 7/9] add static query to fetch additional collectives for reference --- lib/additional-collectives.sql | 90 ++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 lib/additional-collectives.sql diff --git a/lib/additional-collectives.sql b/lib/additional-collectives.sql new file mode 100644 index 0000000..c210d7a --- /dev/null +++ b/lib/additional-collectives.sql @@ -0,0 +1,90 @@ +-- Find collectives that left the specified hosts with money raised estimate + -- (projects and events amounts merged into their parent) + WITH target_host_ids AS ( + SELECT unnest(ARRAY[ + 9807, -- foundation + 11049, -- europe + 169078, -- oce-foundation-usd + 729588, -- oce-foundation-eur + 696998, -- opensource + 11004, -- open-source-collective-eur1 + 1012924, -- the-social-change-nest + 766450, -- the-social-change-nest-eu + 98478, -- giftcollective + 820725 -- raft + ]) AS id + ), + -- All credits with target hosts, mapped to parent collective + credits_by_parent AS ( + SELECT + COALESCE(c."ParentCollectiveId", c.id) AS parent_id, + t."HostCollectiveId", + t."hostCurrency", + t."amountInHostCurrency" + FROM "Transactions" t + INNER JOIN "Collectives" c ON c.id = t."CollectiveId" + WHERE t."HostCollectiveId" IN (SELECT id FROM target_host_ids) + AND t."type" = 'CREDIT' + AND t."RefundTransactionId" IS NULL + AND t."deletedAt" IS NULL + AND c."deletedAt" IS NULL + ), + -- Parent IDs that had credits (directly or via projects/events) + parent_ids_with_credits AS ( + SELECT DISTINCT parent_id FROM credits_by_parent + ), + -- Parent collectives that are no longer hosted by target hosts + former_collectives AS ( + SELECT + c.id, + c.slug, + c.name, + c.type, + c."HostCollectiveId" AS current_host_id + FROM "Collectives" c + WHERE c."deletedAt" IS NULL + AND c.type NOT IN ('PROJECT', 'EVENT') + AND c.id IN (SELECT parent_id FROM parent_ids_with_credits) + AND (c."HostCollectiveId" IS NULL OR c."HostCollectiveId" NOT IN (SELECT id FROM target_host_ids)) + -- Comment out the next line to INCLUDE collectives that moved to another host + AND c."HostCollectiveId" IS NULL + ), + -- Sum credits per parent, pick the host with highest total + money_raised AS ( + SELECT DISTINCT ON (parent_id) + parent_id, + "HostCollectiveId" AS former_host_id, + "hostCurrency" AS currency, + SUM("amountInHostCurrency") OVER (PARTITION BY parent_id, "HostCollectiveId") AS total + FROM credits_by_parent + WHERE parent_id IN (SELECT id FROM former_collectives) + ORDER BY parent_id, SUM("amountInHostCurrency") OVER (PARTITION BY parent_id, "HostCollectiveId") DESC + ), + latest_fx_rates AS ( + SELECT DISTINCT ON ("from") + "from", + rate + FROM "CurrencyExchangeRates" + WHERE "to" = 'USD' + ORDER BY "from", "createdAt" DESC + ) + SELECT + fc.id, + fc.slug, + fc.name, + fc.type, + former_host.slug AS former_host_slug, + fc.current_host_id, + current_host.slug AS current_host_slug, + mr.currency, + ROUND((mr.total / 100.0)::numeric, 2) AS amount_raised, + ROUND(((mr.total / 100.0) * COALESCE(fx.rate, 1))::numeric, 2) AS amount_raised_usd + FROM former_collectives fc + LEFT JOIN "Collectives" current_host ON current_host.id = fc.current_host_id + LEFT JOIN money_raised mr ON mr.parent_id = fc.id + LEFT JOIN "Collectives" former_host ON former_host.id = mr.former_host_id + LEFT JOIN latest_fx_rates fx ON fx."from" = mr.currency + WHERE true + -- Comment out the next line to INCLUDE collectives with less than 100 USD raised + AND ((mr.total / 100.0) * COALESCE(fx.rate, 1)) >= 100 + ORDER BY former_host.slug, amount_raised_usd DESC NULLS LAST; From 58df27fa49dc49c9c572c8493c396343893cfcc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Tue, 27 Jan 2026 21:10:30 +0100 Subject: [PATCH 8/9] update fetch-data --- .eslintrc.json | 2 +- scripts/fetch-data.ts | 132 ++++++++++++++++++++++++++---------------- utils/rate-limiter.ts | 83 ++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 51 deletions(-) create mode 100644 utils/rate-limiter.ts diff --git a/.eslintrc.json b/.eslintrc.json index fb35e9a..e62a3ad 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -110,7 +110,7 @@ }, "overrides": [ { - "files": ["scripts/*.ts"], + "files": ["scripts/*.ts", "utils/rate-limiter.ts"], "rules": { "no-console": "off" } diff --git a/scripts/fetch-data.ts b/scripts/fetch-data.ts index 153bfe1..36fef98 100644 --- a/scripts/fetch-data.ts +++ b/scripts/fetch-data.ts @@ -1,7 +1,6 @@ import fs from 'fs'; import path from 'path'; -import Bottleneck from 'bottleneck'; import dayjs from 'dayjs'; import dayjsPluginIsoWeek from 'dayjs/plugin/isoWeek'; import dayjsPluginUTC from 'dayjs/plugin/utc'; @@ -23,36 +22,37 @@ for (const env of ['local', process.env.NODE_ENV || 'development']) { import { initializeApollo } from '../lib/apollo-client'; import { accountBySlugQuery, accountsQuery, totalCountQuery } from '../lib/graphql/queries'; +import { rateLimiter } from '../utils/rate-limiter'; import { getAllCollectiveStats } from '../utils/stats'; dayjs.extend(dayjsPluginUTC); dayjs.extend(dayjsPluginIsoWeek); -const apolloClient = initializeApollo({ fetch: nodeFetch }); +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// Wrap fetch to track rate limit headers +const fetchWithRateLimit = async (url, options) => { + const response = await nodeFetch(url, options); + rateLimiter.update(response.headers); + return response; +}; + +const apolloClient = initializeApollo({ fetch: fetchWithRateLimit }); const hasValidStats = account => account.ALL?.totalAmountReceivedTimeSeries && account.PAST_YEAR?.totalAmountReceivedTimeSeries && account.PAST_QUARTER?.totalAmountReceivedTimeSeries; -// Rate limiter: 60 requests per minute -const limiter = new Bottleneck({ - reservoir: 60, - reservoirRefreshAmount: 60, - reservoirRefreshInterval: 60 * 1000, // 1 minute - maxConcurrent: 5, -}); - -async function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - async function graphqlRequest(query, variables: any = {}): Promise<{ data: any; elapsed: string }> { - const requestId = variables.slug || variables.offset || 'unknown'; - return limiter.schedule(async () => { - const maxRetries = 5; - const requestStart = Date.now(); + await rateLimiter.waitIfNeeded(); + const maxRetries = 5; + const requestStart = Date.now(); + + try { for (let attempt = 1; attempt <= maxRetries; attempt++) { const attemptStart = Date.now(); try { @@ -61,28 +61,63 @@ async function graphqlRequest(query, variables: any = {}): Promise<{ data: any; return { data, elapsed }; } catch (error) { const elapsed = ((Date.now() - attemptStart) / 1000).toFixed(2); - const is429 = error.message?.includes('429'); const statusCode = error.networkError?.statusCode || error.statusCode; + const is429 = statusCode === 429 || error.message?.includes('429'); const bodyText = error.networkError?.bodyText || error.bodyText; - console.error(`[${requestId}] Attempt ${attempt}/${maxRetries} failed after ${elapsed}s:`, error.message); - console.error(`[${requestId}] Variables:`, JSON.stringify(variables)); + const headers = error.networkError?.response?.headers; + console.error(`Request failed after ${elapsed}s:`, error.message); + console.error(`Variables:`, JSON.stringify(variables)); if (statusCode) { - console.error(`[${requestId}] Status code: ${statusCode}`); + console.error(`Status code: ${statusCode}`); } if (bodyText) { - console.error(`[${requestId}] Response body: ${bodyText.substring(0, 500)}`); + console.error(`Response body: ${bodyText.substring(0, 500)}`); + } + if (headers) { + console.error('Response headers:', Object.fromEntries(headers.entries?.() || [])); } - if (attempt < maxRetries) { - // Exponential backoff, longer for rate limit errors - const backoff = is429 ? Math.pow(2, attempt) * 1000 : attempt * 500; - console.log(`[${requestId}] Retrying in ${backoff}ms...`); + + // Only retry on 429 rate limit errors, let other errors trigger split + if (is429 && attempt < maxRetries) { + const backoff = Math.pow(2, attempt) * 1000; + console.log(`Rate limited, retrying in ${backoff}ms (attempt ${attempt}/${maxRetries})...`); await sleep(backoff); + } else { + throw error; } } } - throw new Error(`Failed to fetch data after multiple retries (${requestId})`); - }); + throw new Error(`Failed to fetch data after multiple retries`); + } finally { + rateLimiter.done(); + } +} + +async function fetchBatchWithSplit(baseVariables: any, offset: number, limit: number, depth = 0): Promise { + const indent = ' '.repeat(depth); + try { + const { data, elapsed } = await graphqlRequest(accountsQuery, { ...baseVariables, offset, limit }); + console.log( + `${indent}Fetched offset ${offset}, limit ${limit}: ${data.accounts.nodes.length} accounts in ${elapsed}s`, + ); + return data.accounts.nodes; + } catch (error) { + if (limit <= 1) { + console.error(`${indent}Failed to fetch single account at offset ${offset}, skipping: ${error.message}`); + return []; + } + // Split the batch in half and retry each half + const half = Math.ceil(limit / 2); + console.log( + `${indent}Splitting batch at offset ${offset} (limit ${limit}) into two halves of ${half} and ${limit - half}`, + ); + const [firstHalf, secondHalf] = await Promise.all([ + fetchBatchWithSplit(baseVariables, offset, half, depth + 1), + fetchBatchWithSplit(baseVariables, offset + half, limit - half, depth + 1), + ]); + return [...firstHalf, ...secondHalf]; + } } async function fetchDataForPage(host) { @@ -92,7 +127,7 @@ async function fetchDataForPage(host) { const yearFrom = dayjs.utc().subtract(12, 'month').startOf('month').toISOString(); const yearTo = dayjs.utc().subtract(1, 'month').endOf('month').toISOString(); - const pageSize = 10; + const pageSize = 16; const baseVariables = { host: hostSlugs ? hostSlugs.map(s => ({ slug: s })) : { slug }, currency, @@ -102,37 +137,34 @@ async function fetchDataForPage(host) { yearTo, }; - // First request to get total count + // First request to get total count and first batch const { data: firstData } = await graphqlRequest(accountsQuery, { ...baseVariables, offset: 0, limit: pageSize }); const totalCount = firstData.accounts.totalCount; + console.log(`Total accounts to fetch: ${totalCount} (batch size: ${pageSize})`); + if (totalCount <= pageSize) { return firstData; } - // Calculate pages needed (excluding first page already fetched) - const remainingPages = Math.ceil((totalCount - pageSize) / pageSize); - const offsets = Array.from({ length: remainingPages }, (_, i) => (i + 1) * pageSize); + // Calculate remaining batches needed + const remainingCount = totalCount - pageSize; + const remainingBatches = Math.ceil(remainingCount / pageSize); + const batches = Array.from({ length: remainingBatches }, (_, i) => ({ + offset: (i + 1) * pageSize, + limit: Math.min(pageSize, totalCount - (i + 1) * pageSize), + })); - console.log(`Fetching ${totalCount} accounts in ${remainingPages + 1} pages (page size: ${pageSize})`); + console.log(`Fetching ${totalCount} accounts in ${remainingBatches + 1} batches`); - let fetchedPages = 0; - const fetchPage = async (offset: number) => { - const { data, elapsed } = await graphqlRequest(accountsQuery, { ...baseVariables, offset, limit: pageSize }); - fetchedPages++; - console.log( - `Page ${fetchedPages}/${remainingPages}: offset ${offset}, fetched ${data.accounts.nodes.length} in ${elapsed}s`, - ); - return data.accounts.nodes; - }; - - // Fetch all remaining pages concurrently with rate limiting + // Fetch all remaining batches concurrently with rate limiting and split-on-failure const startTime = Date.now(); - const pageResults = await Promise.all(offsets.map(fetchPage)); - const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); + const batchResults = await Promise.all( + batches.map(batch => fetchBatchWithSplit(baseVariables, batch.offset, batch.limit)), + ); + const allNodes = [firstData.accounts.nodes, ...batchResults].flat(); - // Merge all nodes - const allNodes = [firstData.accounts.nodes, ...pageResults].flat(); + const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); console.log(`Total: fetched ${allNodes.length} accounts in ${elapsed}s`); return { diff --git a/utils/rate-limiter.ts b/utils/rate-limiter.ts new file mode 100644 index 0000000..6258ef8 --- /dev/null +++ b/utils/rate-limiter.ts @@ -0,0 +1,83 @@ +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +// Simple API-driven rate limiter that spaces requests evenly across the window +export const rateLimiter = { + remaining: 100, + limit: 100, + resetAt: 0, // Unix timestamp in seconds + nextAllowedAt: 0, // Next request allowed at (ms timestamp) + running: 0, + maxConcurrent: 5, + minRemaining: 0, // Wait for reset when remaining drops to this + acquireLock: Promise.resolve(), // Serialize acquire calls + + async waitIfNeeded() { + // Serialize access so requests are released one at a time with proper spacing + const previousLock = this.acquireLock; + let releaseLock: () => void; + this.acquireLock = new Promise(resolve => { + releaseLock = resolve; + }); + + await previousLock; + + try { + // Wait if too many concurrent requests + while (this.running >= this.maxConcurrent) { + await sleep(50); + } + + // If rate limit is exhausted, wait for reset + if (this.remaining <= this.minRemaining && this.resetAt > 0) { + const waitMs = this.resetAt * 1000 - Date.now() + 1000; // +1s buffer + if (waitMs > 0) { + // console.log(`[Rate Limit] Waiting ${(waitMs / 1000).toFixed(1)}s for API reset (${this.remaining}/${this.limit} remaining)`); + await sleep(waitMs); + this.remaining = this.limit; + this.nextAllowedAt = 0; + } + } + + // Wait until next allowed time (spaced evenly) + const now = Date.now(); + if (this.nextAllowedAt > now) { + await sleep(this.nextAllowedAt - now); + } + + // Calculate interval for next request + if (this.resetAt > 0 && this.remaining > this.minRemaining) { + const msUntilReset = this.resetAt * 1000 - Date.now(); + if (msUntilReset > 0) { + const interval = msUntilReset / this.remaining; + this.nextAllowedAt = Date.now() + interval; + } + } + + this.running++; + } finally { + releaseLock(); + } + }, + + update(headers: { get: (name: string) => string | null }) { + const remaining = headers.get('x-ratelimit-remaining'); + const limit = headers.get('x-ratelimit-limit'); + const reset = headers.get('x-ratelimit-reset'); + + if (remaining !== null) { + this.remaining = parseInt(remaining); + this.limit = parseInt(limit) || 100; + this.resetAt = parseInt(reset) || 0; + + // const msUntilReset = this.resetAt * 1000 - Date.now(); + // const interval = msUntilReset > 0 && this.remaining > 0 ? (msUntilReset / this.remaining).toFixed(0) : '?'; + // console.log(`[Rate Limit] ${this.remaining}/${this.limit} remaining, ${(msUntilReset / 1000).toFixed(0)}s until reset, ~${interval}ms/req | ${this.running} running`); + } + }, + + done() { + this.running--; + }, +}; From a7f4a2beed16155700ded749cbff612a80cfdd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Hodierne?= Date: Wed, 28 Jan 2026 08:34:28 +0100 Subject: [PATCH 9/9] update data fetching --- lib/additional-collectives.sql | 48 ++++++++++++++++++---------------- lib/additional-collectives.ts | 4 +-- scripts/fetch-data.ts | 11 +++++--- utils/rate-limiter.ts | 8 +++--- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/lib/additional-collectives.sql b/lib/additional-collectives.sql index c210d7a..38f9b15 100644 --- a/lib/additional-collectives.sql +++ b/lib/additional-collectives.sql @@ -1,4 +1,4 @@ --- Find collectives that left the specified hosts with money raised estimate + -- Find collectives that left the specified hosts with money raised estimate -- (projects and events amounts merged into their parent) WITH target_host_ids AS ( SELECT unnest(ARRAY[ @@ -14,25 +14,18 @@ 820725 -- raft ]) AS id ), - -- All credits with target hosts, mapped to parent collective - credits_by_parent AS ( - SELECT - COALESCE(c."ParentCollectiveId", c.id) AS parent_id, - t."HostCollectiveId", - t."hostCurrency", - t."amountInHostCurrency" + -- Collectives that had credits with target hosts (to identify former members) + collectives_with_target_host_history AS ( + SELECT DISTINCT COALESCE(c."ParentCollectiveId", c.id) AS parent_id FROM "Transactions" t INNER JOIN "Collectives" c ON c.id = t."CollectiveId" WHERE t."HostCollectiveId" IN (SELECT id FROM target_host_ids) AND t."type" = 'CREDIT' AND t."RefundTransactionId" IS NULL + AND t."isRefund" IS NOT TRUE AND t."deletedAt" IS NULL AND c."deletedAt" IS NULL ), - -- Parent IDs that had credits (directly or via projects/events) - parent_ids_with_credits AS ( - SELECT DISTINCT parent_id FROM credits_by_parent - ), -- Parent collectives that are no longer hosted by target hosts former_collectives AS ( SELECT @@ -44,21 +37,34 @@ FROM "Collectives" c WHERE c."deletedAt" IS NULL AND c.type NOT IN ('PROJECT', 'EVENT') - AND c.id IN (SELECT parent_id FROM parent_ids_with_credits) + AND c.id IN (SELECT parent_id FROM collectives_with_target_host_history) AND (c."HostCollectiveId" IS NULL OR c."HostCollectiveId" NOT IN (SELECT id FROM target_host_ids)) -- Comment out the next line to INCLUDE collectives that moved to another host AND c."HostCollectiveId" IS NULL ), - -- Sum credits per parent, pick the host with highest total + -- All credits across ALL hosts, mapped to parent collective + all_credits_by_parent AS ( + SELECT + COALESCE(c."ParentCollectiveId", c.id) AS parent_id, + t."hostCurrency", + t."amountInHostCurrency" + FROM "Transactions" t + INNER JOIN "Collectives" c ON c.id = t."CollectiveId" + WHERE COALESCE(c."ParentCollectiveId", c.id) IN (SELECT id FROM former_collectives) + AND t."type" = 'CREDIT' + AND t."RefundTransactionId" IS NULL + AND t."isRefund" IS NOT TRUE + AND t."deletedAt" IS NULL + AND c."deletedAt" IS NULL + ), + -- Sum credits per parent across all hosts, pick currency with highest total money_raised AS ( SELECT DISTINCT ON (parent_id) parent_id, - "HostCollectiveId" AS former_host_id, "hostCurrency" AS currency, - SUM("amountInHostCurrency") OVER (PARTITION BY parent_id, "HostCollectiveId") AS total - FROM credits_by_parent - WHERE parent_id IN (SELECT id FROM former_collectives) - ORDER BY parent_id, SUM("amountInHostCurrency") OVER (PARTITION BY parent_id, "HostCollectiveId") DESC + SUM("amountInHostCurrency") OVER (PARTITION BY parent_id, "hostCurrency") AS total + FROM all_credits_by_parent + ORDER BY parent_id, SUM("amountInHostCurrency") OVER (PARTITION BY parent_id, "hostCurrency") DESC ), latest_fx_rates AS ( SELECT DISTINCT ON ("from") @@ -73,7 +79,6 @@ fc.slug, fc.name, fc.type, - former_host.slug AS former_host_slug, fc.current_host_id, current_host.slug AS current_host_slug, mr.currency, @@ -82,9 +87,8 @@ FROM former_collectives fc LEFT JOIN "Collectives" current_host ON current_host.id = fc.current_host_id LEFT JOIN money_raised mr ON mr.parent_id = fc.id - LEFT JOIN "Collectives" former_host ON former_host.id = mr.former_host_id LEFT JOIN latest_fx_rates fx ON fx."from" = mr.currency WHERE true -- Comment out the next line to INCLUDE collectives with less than 100 USD raised AND ((mr.total / 100.0) * COALESCE(fx.rate, 1)) >= 100 - ORDER BY former_host.slug, amount_raised_usd DESC NULLS LAST; + ORDER BY amount_raised_usd DESC NULLS LAST; diff --git a/lib/additional-collectives.ts b/lib/additional-collectives.ts index a569a29..72191d8 100644 --- a/lib/additional-collectives.ts +++ b/lib/additional-collectives.ts @@ -15,11 +15,11 @@ export const additionalCollectiveSlugs = [ 'adviseccr', 'afchildhoodjoyfund', 'affect', + 'akibaparty', 'alaskansocial', 'amazmod-33', 'amazon-workers-for-democracy', 'amiberry', - 'ampalibe', 'androidide', 'android-password-store', 'angular-fullstack', @@ -614,11 +614,11 @@ export const additionalCollectiveSlugs = [ 'wwwdxmarathoncom', 'wwwmanhoodpagcouk', 'xplr', + 'xrhackney', 'xr-namur', 'yfcrm', 'yoga-para-la-gente', 'york-community-energy', 'youngstownactioncenter', - 'zane-access-co', 'zipstream', ]; diff --git a/scripts/fetch-data.ts b/scripts/fetch-data.ts index 36fef98..3a4c387 100644 --- a/scripts/fetch-data.ts +++ b/scripts/fetch-data.ts @@ -77,10 +77,13 @@ async function graphqlRequest(query, variables: any = {}): Promise<{ data: any; console.error('Response headers:', Object.fromEntries(headers.entries?.() || [])); } - // Only retry on 429 rate limit errors, let other errors trigger split - if (is429 && attempt < maxRetries) { - const backoff = Math.pow(2, attempt) * 1000; - console.log(`Rate limited, retrying in ${backoff}ms (attempt ${attempt}/${maxRetries})...`); + // Retry on 429 (rate limit) and 503 (server overload) + const is503 = statusCode === 503; + const shouldRetry = (is429 || is503) && attempt < maxRetries; + + if (shouldRetry) { + const backoff = is429 ? Math.pow(2, attempt) * 1000 : attempt * 1000; + console.log(`Retrying in ${backoff}ms (attempt ${attempt}/${maxRetries})...`); await sleep(backoff); } else { throw error; diff --git a/utils/rate-limiter.ts b/utils/rate-limiter.ts index 6258ef8..ec970e8 100644 --- a/utils/rate-limiter.ts +++ b/utils/rate-limiter.ts @@ -4,13 +4,13 @@ function sleep(ms: number) { // Simple API-driven rate limiter that spaces requests evenly across the window export const rateLimiter = { - remaining: 100, + remaining: 100, // Local estimate (decremented on start, synced on response) limit: 100, + minRemaining: 1, // Buffer to avoid hitting exact limit resetAt: 0, // Unix timestamp in seconds nextAllowedAt: 0, // Next request allowed at (ms timestamp) running: 0, maxConcurrent: 5, - minRemaining: 0, // Wait for reset when remaining drops to this acquireLock: Promise.resolve(), // Serialize acquire calls async waitIfNeeded() { @@ -47,7 +47,7 @@ export const rateLimiter = { } // Calculate interval for next request - if (this.resetAt > 0 && this.remaining > this.minRemaining) { + if (this.resetAt > 0 && this.remaining > 0) { const msUntilReset = this.resetAt * 1000 - Date.now(); if (msUntilReset > 0) { const interval = msUntilReset / this.remaining; @@ -55,6 +55,8 @@ export const rateLimiter = { } } + // Decrement remaining NOW (API counts on receive, not on response) + this.remaining--; this.running++; } finally { releaseLock();