From a2c31f873d7683eeeacdcc63d49e7c666a0983ea Mon Sep 17 00:00:00 2001 From: "Gu://em_" Date: Fri, 15 May 2026 11:08:23 +0200 Subject: [PATCH] given files --- .env | 8 + .gitignore | 24 + .prettierrc.js | 6 + eslint.config.mjs | 71 + package.json | 31 + public/default-avatar.png | Bin 0 -> 24739 bytes public/default-icon.png | Bin 0 -> 29737 bytes public/favicon.ico | Bin 0 -> 590 bytes public/selector.svg | 14 + server/.env | 37 + server/config/default-canvas-250.txt | 1 + server/config/default-canvas-50.txt | Bin 0 -> 1563 bytes server/config/rate-limits.config.json | 54 + server/config/rooms.config.json | 46 + server/docker-compose.yml | 56 + server/openapi/openapi.json | 5503 +++++++++++++++++++++++ src/components/notifications/index.html | 12 + src/components/rooms/index.html | 10 + src/components/rooms/message.html | 8 + src/components/rooms/upsert.html | 70 + src/components/rooms/user-event.html | 3 + src/components/students/update.html | 42 + src/pages/complete/epita/index.html | 12 + src/pages/complete/epita/index.js | 2 + src/pages/debug/debug.html | 58 + src/pages/debug/debug.js | 122 + src/pages/debug/index.css | 128 + src/pages/debug/utils.js | 57 + src/pages/index.css | 78 + src/pages/index.html | 239 + src/pages/index.js | 6 + src/pages/styles.less | 1080 +++++ src/pages/utils.js | 83 + src/rooms/canvas/index.js | 7 + src/rooms/canvas/utils.js | 467 ++ src/rooms/chat/index.js | 4 + src/rooms/chat/utils.js | 6 + src/rooms/index.js | 10 + src/rooms/utils.js | 6 + src/students/index.js | 5 + src/students/utils.js | 5 + src/utils/auth.js | 6 + src/utils/notify.js | 64 + src/utils/rateLimits.js | 3 + src/utils/redirect.js | 4 + src/utils/streams.js | 9 + vite.config.js | 44 + yarn.lock | 1957 ++++++++ 48 files changed, 10458 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 .prettierrc.js create mode 100644 eslint.config.mjs create mode 100644 package.json create mode 100644 public/default-avatar.png create mode 100644 public/default-icon.png create mode 100644 public/favicon.ico create mode 100644 public/selector.svg create mode 100644 server/.env create mode 100644 server/config/default-canvas-250.txt create mode 100644 server/config/default-canvas-50.txt create mode 100644 server/config/rate-limits.config.json create mode 100644 server/config/rooms.config.json create mode 100644 server/docker-compose.yml create mode 100644 server/openapi/openapi.json create mode 100644 src/components/notifications/index.html create mode 100644 src/components/rooms/index.html create mode 100644 src/components/rooms/message.html create mode 100644 src/components/rooms/upsert.html create mode 100644 src/components/rooms/user-event.html create mode 100644 src/components/students/update.html create mode 100644 src/pages/complete/epita/index.html create mode 100644 src/pages/complete/epita/index.js create mode 100644 src/pages/debug/debug.html create mode 100644 src/pages/debug/debug.js create mode 100644 src/pages/debug/index.css create mode 100644 src/pages/debug/utils.js create mode 100644 src/pages/index.css create mode 100644 src/pages/index.html create mode 100644 src/pages/index.js create mode 100644 src/pages/styles.less create mode 100644 src/pages/utils.js create mode 100644 src/rooms/canvas/index.js create mode 100644 src/rooms/canvas/utils.js create mode 100644 src/rooms/chat/index.js create mode 100644 src/rooms/chat/utils.js create mode 100644 src/rooms/index.js create mode 100644 src/rooms/utils.js create mode 100644 src/students/index.js create mode 100644 src/students/utils.js create mode 100644 src/utils/auth.js create mode 100644 src/utils/notify.js create mode 100644 src/utils/rateLimits.js create mode 100644 src/utils/redirect.js create mode 100644 src/utils/streams.js create mode 100644 vite.config.js create mode 100644 yarn.lock diff --git a/.env b/.env new file mode 100644 index 0000000..29ee83a --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +VITE_HOST="localhost" +VITE_PORT="8080" +VITE_URL="http://${VITE_HOST}:${VITE_PORT}" + +VITE_API_URL="http://localhost:3333" +# VITE_API_URL="https://eplace.assistants.epita.fr" +VITE_AUTH_URL="https://cri.epita.fr" +VITE_CLIENT_ID="assistants-atelier-js" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..fcc77f6 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,6 @@ +module.exports = { + tabWidth: 4, + useTabs: false, // Use spaces instead of defaults tabs + semi: true, // Force semilicons + printWidth: 80, // Max width +}; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..a315e71 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,71 @@ +import jestPlugin from "eslint-plugin-jest"; +import globals from "globals"; +import prettierPlugin from "eslint-plugin-prettier"; // Import the Prettier plugin +import eslintComments from "eslint-plugin-eslint-comments"; +import js from "@eslint/js"; + +const cleanGlobals = (obj) => + Object.fromEntries( + Object.entries(obj).map(([key, val]) => [key.trim(), val]), + ); + +export default [ + js.configs.recommended, // Nice defaults rules + { + files: ["**/*.js", "**/*.mjs"], // Apply to .js and .mjs files + languageOptions: { + sourceType: "module", + globals: { + AudioWorkletGlobalScope: "readonly", + ...cleanGlobals(globals.node), + ...cleanGlobals(jestPlugin.environments.globals.globals), + ...cleanGlobals(globals.browser), + }, + }, + plugins: { + prettier: prettierPlugin, // Add Prettier plugin correctly + jest: jestPlugin, // Jest plugin + "eslint-comments": eslintComments, // Add plugin to detect cheats + }, + rules: { + "eslint-comments/no-use": ["error", { allow: [] }], // Disallow disable rules + curly: ["error", "all"], // Enforce curlies in conditionnal blocks + "brace-style": ["error", "1tbs"], + "max-statements-per-line": ["error", { max: 1 }], + semi: ["error", "always"], // Enforce semicolons + "prefer-const": "error", // Prefer const over let + "no-undef": "error", // Detect definition + "no-unused-vars": [ + "error", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + // Detect error of line width (but can't fix them without the prettier) + "max-len": [ + "error", + { + code: 80, + tabWidth: 4, + ignoreComments: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + }, + ], // More rules to complete with the existants in .prettierrc.js file + "prettier/prettier": ["error"], // Enforce Prettier formatting + "padding-line-between-statements": [ + // Create nice looking paddings between statements + "error", + { + blankLine: "always", + prev: ["const", "let", "var", "if", "for", "while", "do"], + next: "*", + }, + { + blankLine: "any", + prev: ["const", "let", "var"], + next: ["const", "let", "var"], + }, + ], // Requires blank lines between the given 2 kinds of statements + }, + }, +]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..85926a4 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "eplace-client", + "private": true, + "version": "1.0.0", + "scripts": { + "dev": "vite", + "debug": "vite --mode debug" + }, + "devDependencies": { + "eslint": "^9.25.1", + "eslint-config-prettier": "^8.8.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "less": "^4.1.3", + "vite": "^4.2.0" + }, + "dependencies": { + "axios": "^1.4.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jest": "^28.11.0", + "eslint-plugin-prettier": "^5.2.6", + "jquery": "^3.6.4", + "jwt-decode": "^3.1.2", + "node-fetch": "^3.3.1", + "prettier": "^3.5.3", + "socket.io-client": "^4.6.1", + "uuid": "^9.0.0" + } +} diff --git a/public/default-avatar.png b/public/default-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..7c0db888350de90dcb6fab7f1ecfeee1e4ffea29 GIT binary patch literal 24739 zcmeEs^28yhSTW(F6*`p}0%Y;>9gMiWDml+=@HF-D%O_?(XjHm-l=B zi~G6xDLcD!&NF9@&F&NYRY4LTmkJjE0N_hYi7NvDm=FK}y$l=ec_e1ZB?kaN-TNx1 zD)G2FvHZ8`Us_giP*iPn@pKR)YwD3})i44f>;dK_)pZ7Hh_4NtUoH{;jHa)I@w{aLOoI7ik-7&-iljh)F$ zy6!9;N{NZkGG0&fwGmcNwQ)YFPalhfCK#G|%j#`r{@N>wJa5gv>#q$|Hf-~A*~$qy ztVq0UL(C`m99920Ys$VKYH$(PGUZi?w{S@}w;`8@5cAlP|B7Ou07(;(Zn|Z3+Pxg(ql{a6Uo@a%m)MZ!nw;;z;A|4;^ z9nmeJ@2_@l4#%!Go723#AMY>ybSm1-jce@%l#W$i3?EAP+h3(+2xA@;w&8J%`k_B)xa{(<#nc_4gk8_f(S z{C-$N)c@Fj{~HyVr>(n^=)H_cidNJbgV}aQg%!KG{t#4|3UmlkGcM8BA9Vdb>|r_L zW!-0QG7oqAEvxAYR)c=ky;yGDD*8Pg;&jlKdOC*KtB((m)Y!-h*eQdjtLs?uDPJtt zY~}}_&sFSJN4HsiJDVy!?8-PE%-?TLI_k~dYDnCuOBnI7SuP0gcQ#+h3>yFKw$q=# z-I_X^|D5F)Nr@zvhe==9HP~*SW z7In5*;VGd7Q#E!~FbtB{8LUd4Y)UN-aA=No>ko(i)H1J)51eYv>@QE4DES$0XWf+O zH(DAL^TR3A%O%R@$KGUVRU~Y_A)>YV8Up~(bCebrRdt&^Sa5e!ovJ^&Nq4vE>Yv6; z`l9QffR4oEdHH$`#aJ+WOj?0OLLn$z0Z#OWnA`6=o(zbAPV3(n8wMTAyH(ppn{Hl9 z;IR;4N=<~IVe+HgV0d(kh_s;A#1^;qE+*e_MYGp z%5wnz|KI<&!2e?wNH_kXU@NCDVut~MXo-G{e4q0v&)*dtU|7mnIxeNEsy;Y}3Mh+= zrUn{2BnAKgMKKyLjdI970{|3b5%ix6f7fCGKuX^=3rt?&0hU4FFQg`zN5lXK82lnD z2pt9R2}!744gi?d{}S~D0IJAeTH?AR004WL5HhrmH_HR4068}G0m7^x33kBW80cr3 zP)z_VA?p7A&%AON;C=7u>DkT}2FzMbgzB{^Il06y<5=>}`Yfe39UOp`(U-^XKVqmB z-%{a6{eiMF0O0jswy0B?B^|FXAQSjQCjkJ> zpPq%thE)|HISeK60Bw9H$f4qQqoq~v0{6yIKti~^tYOO_aR7+S4Bcq+8v$`#B>?od z*D)mY_us<)oH7Ey&p#gMmJ2eY>pBVT;^+X>ob)mzc5sLgD?Nbd1=COcIucH^NIC$K z|M|PN{HotCP+;7+79Q^iQD9ZLN(-M;>BgV+boTbig#G^7PqPPr3o!A=e%XA(YV3mz z$QJj`Xhpk~i%h=P>oxMk23(8WAkVyaEGPRDE6s;eKU$VlyJtO_JnqlnnGNc0KTv$m zr2{Nyy$VR(>>8-FOLW}RHK!Oz{OEaU#1-vt@hqSd(c4|rSsCB2#XZH4C0g+}i~6mR zBR0UTRA|aCcR>r!Y$%a$iZFDuzjY?`<=eq6Nqm65L#qqV*(Ou%;Kf`MedUSy9n;s5)3%8B{q`dg^N`*xJ@aPXXG+S4{nM``^coyZZ&u-3N6ih@1 zfHz!g))@xR(a?r&4#@>&F{SQP*w-XU_yb_PEGPD31BYyzXA}vaEo%6jFFFkA#Q=W^ z31qe^Z74GB4O-}P;twy3cpVgqVikJ;tnfTpU@9@>lR2DY)y?AS4fDOn;mX7P! zpXRPvrm7_xO=i~b^ug(CC{SU=4g-2ycy^bC23Hh&>IGW*w3*f4Tg>E}gU}QJf0ctb z6E#BZPORk&j9#lP9h#LA*PG=L6V znQC6Bn*g0=rfT7pch)Vm*h1=64iVtp7_0YuRma@SUy^FO#=n8>3Y+ZdWT*hnm>7!} zS$It%->%drvcA}!(1~txCKbQOFUWqri-XYE?TiSh6mC`d5$Pa1-X`JbRc^xE_-uux z2}E+-^*iWN(fgZ|z3D|A%Cc|_K!d}?5ykn2OTLct?)2Hc47~o#j4K<62jI@P6EavH zBL`}mQ*&OPof2IbNpZG*0#u}UoIrmX_}ndJ*1Fm+>Vi4ygN+0LZ{)!;W9s~AA7lj< zv?|`ksu3+Na~jwS-k@IQPN;yc-tcqEv_w z;6@{k`C4O42|aP1GLwwX{ay?ON#!mR7&jl4q$J?}tGtM{=XBlta!CF6ri^9kyig9B z0&9K_8j?U}pb>8?Z+GZ^J$fDyjb>tc>WFytw4_n2-)z*GlwAI-(OJcSFP zKyu+i_SBj>j4m41@AC^|(UCD12)?4}?WLIYxa4Pne_h8F6#lsH&6CO0y)bc4c?;Ry zM>S!Uv~Bd-$#seqW92s>hWz3(P(#nVrjvmWO!%7QD|-0u6|xs;d(BY|nKPVGRQ|?* zZLsN7QVz&hyFbld*On|Pv-J8qOAr09iY*r8oj8}qqWe2buV0^!@9Gz{j(W$X6 zh|LyNCeCgtkr|+=Ej}r06G?u4WTp|SMxDNhVBF(~PG??`wk;Sf+f^9Kr9a!hD-ZcJ zgAQR%`sU-(vnpZH8&QwoP)WC~oDd=QL}<8?(#!6L?&xx3fX>#*FiPA?s6i(j zqnS8SDC_Jo3+LkQ$v&j|XBVgOfmvKtMNlyRe!etk$#uScZ>grXTQpl{7Tp>R=l)Zo znacO@k&|`s8x1}z87!ICO*FRvv`F>tPid@S?L2JqDo%@rx2ZmrMVlu`1UQkQ4I31* zB8wCDYgr55^u2vjQ23Et%3NF^(=4cK5_G_rU6IWQuB|iL-TIoaw$`Ss!1Q1Un9q|; zb!RV6_GWJ~wVk!em%-b5Wz@*hG5=9ddsFe(dW`Y_*0Of}D#uo^&d^p47|g4dGhmr_ z0ly(w+`~P(dbJr-QA`ZGn~^N>qZ>#N<^$N4%H#?12?(M(#6@GDxTwSJ#`NObyHR1N z0b!fIyEnz6NWx@4IpvY~4U_1VObi8*xpsnLhr3D(%}&(xvKXax!*{Yi7tsEY!gC#; zM!%dQCOn#^9ewBTZwEewZf{{2CIb~7U#0?st`iz9)YL%Mnn_8f>~aci5FOV8Xsx63>G~4 z_Gdmy4y1a8QogDhIoyv7lg_%GozuAcgkR5w(WhCm2cmi)$$<(1G~X)dT}^P{kzn=* zgl;ujqv<#HI>>+)^UE)JpEl`Si$?m#lB<^P>0ReK&Z=*j5_|z=R9~wk-{mD^nTKNb zY01u$K@Q8dORL|=6g7`^=N6G`vuTBz?Yf>dB-+O19D1T4-N+l(rr6D86-Pb)Q3|w5 z>a}2bUCeUR`c#Noh2B~ppxd`}av3!0C4XTDEXqTuU5>Qzqw(V1{s8w5bTaRSA$x;4 z-V|aXD%=l-_;|3Kw(%G+#q~Qxq^)SHyeVe#(yt^gxH01CW?yhw`xvET@GJIA*@Gd`RbEJP)vJA2@; z5fs?BF0x1%!UhD(JoRlkQgWONrSVGdZBb3^%_%#`O;Uknb{f1iE&DWFLmwqY7yyji zn8g?h$u_a^=4SOvPv!Kc-R};k8!B?m>c6p<7R*#M&-!84t1*b4bG~3#N-|$`Jn~vB zibFvXP{}ui^W6`e+w$)tXrmnY{or_pO zVrdJX36H4PyvttzP*&f7CyPpF*K0&4yi+U3cR?L<)uqpj>wh#WQvM5o2EidrX8Zz`*ClVJwof8 zQ&iRG@8!nX9c>~qB4z2qc1#d}Dqi30a_^?2gyR_;KSj-1UH_!JM?HT*s|fBQ?P$o; z$Vh7GXa|(rwaz*}4Zgh^>BRfH1}GnjkZ0@C@KeWoOg2>Wx!m?Q8;rupESca}KmgZ- z_@!l2fyv~H#iqQHCJP(VzWEan>$%cg_0q2WOdLGeFKVt+)m5!n(pbs1Dx zjVLw)Y|<8W+FwM@>@vZ_SlJYIdEoM{*D=5^Ueya>DpvC>K{BxEjcz|emX7X0NoYNo z64J%$+V}ufX)_CHBnx+wsq1hV6sztTRIbi)v^(Ph@9wJYed4*4Y@RDNUNEYCtCvwf z$q822p(t>z! zBg|dRGv$5zeIZurTSnudN*zcM*9Vvc4W@+JV)@e757WjVR zZ_8HCdsVN#L3}j7E+lH*?YwticQ=xP%KZ&xIQr-lc(O#DU$)~sJ*k#pSO4{Q9~z{x z%3x>U(LzGthF;*$4#czswQVp zG(Av^R1w1Y7mOYBq9N9CWvtHerC%0~iY8HEPAk4 zw>?lQ)OZfqS?Fq!t)dpe?X9>kKG#8ccJKZ}_pLnQN3P#aAV`!N6RFJ-9kFAK7Z3|? zv~@g5$fQDU77H?N8Be?cmuxV07=fisgDdZeoDA&YH~=iOPjfykleb6La|@WrT%Hm; zHUpozu4{K>Zcgn|eeVK8l5%G5B)u+o+&r&BwR7wdg2t% z(z^)L4@9I8+9#6%Oeu;5C~nH7zd<7eYv)~nQmd?EA%caztSgM2130FqmSeB$B&-^$ z#+7`@g3Mznt_T+^$57=p0pmUKy_B2|(Dd8&ggUd;q1|MT@HUxL4{J>|a>Enz6I+Fb zOXHNy#3rgcAmw(okrvPKqIoRvs?eMaRusRt{wR7O?1tc-XA;7b)%??HHSp^~O;n>+ zS)g@#w<$#AFua<8L|j#hD{iaXwfOWiGN? zsHuHYwbRp3skBAhO#Y+E#pF0RU^~^mH41z1uU9zpV`c~Rvg2;oekw@~eXNSp*KTy# zNg{V$REmA2TlH;ba+GpFnKOG!aFCGT14??YQ2{iV9c=5!UBw)a^>{)4Y)^ffBx%WA z)a>2x#&c4}aw#R{^-o|I5u}YWU>u3JUD+&%qE{uInaj%HP7FF#(OeHqB8%;QQVmVn z>SlO$l&{fQUa%hJZk1mh@+5-TL|BwYJ*12+@YJn$VDIC0;KM-cKT&2t4AM1y==2!f zN%RhJ5U@h0X5KJ0xq`M(F6}CaJpT|4#R^jMzCZ2;2mhWOc-E~N6Ju!$nqRkdyjH#2 zJDu(CCp`Pc9Imlz(mgU%+xn+W`RckOMbPKA799F}uKm9mWLCV#iyJi>g&rH*?AhLW zzZAgY<`)fbF6K+>X-piz-%1W2&(faUJ_vu|8TQA3{Dc-j!_5~OwQ{>Al$d)SnpV*h zP4zG%FHON9t%lo1sirUgG~!OQP~hKxnV}sWtowPx>XNL3@#sQrE7<*zoV7kuZr-a) zPxK~||_F>m<{AC@J@*fcz;iVs^hc;5nW)+G%Wc6Sc{u^Y4( zx;zE7Dc{O{j;Mz{wim2Q?x7@e<>KH$d~s7yZ>fAm>y_#k5wt~b5qvvjqXD#Xoas-? z-6|&|hIbRs`39t)s(|vq>mX`Oj9Bc$V7K@c+j>8;hG=I~I-7ON5tj8YN)NVWJeC>BaYQa$A=mYJIWeK4JcD zB=>qF7HSBX<}zHcZfT6W$F}xGUXf~Z7(O_x&Lf(!)Xwq}6kh=5Fxbxs+8Vzv;s5Yu zw;%p_F&2FP-9}X(CF{9R9H7S41H`=1doayS;YTdgUOeT{iEJg`Ef6W722WEK83AVs z>a~UIW!`@u*+Jpl`iS<|48`jG+|cKE5N@OBp=&bm+4U0`=fvE`=xfpfxkSz8!XB^c z{ugHNL_IJksZSYx;}|)L5nGZAm!`?2t#xJ_HAG#K&DvO_6}#^kgRF`74twse?Vo259MU`~28t6# zwQpg4up9zf9?Wsy6sHYF+Z+^xac2t$FB5e+8f=uETTG zyqCc7$%2ty5#Duu|6h+HUf|}hMFhsR`60NE2_>oM|6%E<_m@=MrR=WtrZR|Mbe1{% zqs8>=Cva^@OAAbq0MPUH5RY*euavQMn8wXFc0$8f zCKu;G)Jk1B(dU(>3!x-hOu=H`GaRV53$meUVkU~rd4-3*4cf-*yFLfVLBw-Q=;o{R zKe5{k#`rp>{6N{u@H}ihYSD8wZ=+Y&Wt)`iR)IIqGh;xUQ9pQLPo_NH&(J1Fu>Zr+ zbX{_N>M->pJQ@|A7arbX9ds1J$avo0U38OM+H-O$pBh=AI;*LYK)L27(Sr2kv?H{L z{Xdswo9v81wOk66qm-XgeE&1}1P*eXM_D%Z$vVE?{J!)?@=%s47|r2>UD$r^lyrnp zw#EX^swju+Y}M(kqNsSSbnNK~N&-xCpH8YtIPfrqHW-M#EM?`*v^1Bv9*I>= z!U&g~T(Yf99(KWXfImlX5-D6zR{=sBVt_yEu6X=q)^t-aPVNxji#)q{0fD3$-Icq-d9;t-X6Uey1qj zrYgyIe@YnH@n+)eZ7r2Xj0Wci`!7zW*f1E=R;d0&Khks3_3^*~LE+mRDJW`rC zT$UPsOJl=@ao0(SZYaba0Phb&OC^?QLZ1JGBmCx{V3Bcv0-c@ZxkqTZT`GTu~W$P?T#22VB*)JG8l$s7t&P;%C7eJm;MIk!DjN>Fv5 z3U@&$O`j)X(bj20+vDxCUgo~&%j1%vTtLAo2c$4XIE|w~wp5eGD{dQcrGq7veFJ!Y zNF5QQhwjkWHe5N{7wKZ9jh5Y23a~%7cFE_F0-yi&x>~60J0LS@u8OXmU>2ose1;nP zA6A(r9Fvv-x{$@kL~8$bGDO@>3@}_9Er!!${$imgy5aC)aiL@owMEZ`*)CKM_J-%& zK^_~P(QCqVjxWN={=!bB?rQDd5lh-fpHdT&OsBqHn~Dz}(PF;&-2W5*vqOV;@q_E( zZcKdY7&NEYK+S2vJy!Yb&@1ANL2SOdmNwf=m^XwD^8MMnR5MH{Z%iDW>yQuvPY3+^Wwg##iS*}6K6 z*>9~^_lHMERmaa&-X+U~O{&t;ci$#GgVGSgs%4Z`th83qTHX_OUH&VlvQa-H^7^M_ z(dp+87)mi2Rc-*z~ zCbl5{OY@%MgoCmyC&j(lqNdiDV=(WcNJk(1~hS(`bx zg&@rxd+%|#t1ymSAUI7b;^j*2G0QlKN%Jn%LWAp2)$SXV zoU32I^g;6-EcH)w2q(#fB)LSGs=H@TqY>+@3!!Xn>eGEU_#+1w4{j((Pfrn?^#Oyp zXWZkdR=&|dTnrmKb2VPI zcr2j$?}*9C1se|hs67;}F4T32*tbDH@U1i{fGH1$iww_7%SdcLr&TZZ zV8fn?)C}S3J(cNhY-Ri7eZOlSxj9)=gNA>nmewz8jB;Aho6*!(dPIhWd`q~<@~rkN ztFzZ_9UmTeuMo8>bZ#Wx{hh0)l$+hSq7U)RT;`qAGgymQmh+>~7Uu%P%p3$YO0-P-Sf7|$4_2ws3u?l+8*Q(Os-rhthy!k%wjLV+k2*!u<9F#;N zZOr;xQ@sKle&a{^g}rs?N`#%o+MYu`$F&tAY_CgyV+6^t*j0Nl9KoO8&5~^#+oQMT zXr3!A#_f*&T)H`FQ^}&0wf7y*S1h!2=JmN}RTo^E+g^SZR{hPnx$yM2e`qA7I7;Sq z^BtLX^?w*8+~QRWLx(7CPWn~2z*n;gOHKFJ#ssNDDqK&SJ0&=!t(Wt!hqj^R$p@f5 z?v{vkz_KU$$w|8b(iL~s-SOVWt!sx3yd?y=7S-E67*EK82KXsCxJyrRXlL|V@Hl3SU#WEr@M@Q4?@H}W ze9U_s6fuUWz+bZmi|gGVBftwx$G4$e`69$8Xum}|`gX^N0&P$(k`SG7{J5hd8~>F#dTyGi%Ej}^~r6w7LBXVwq&J2X4NY>PKIic$)N^QFd#z!dz) z`S}?gDlc=9&bY}~KA*fcODX~HnKuo?Bev{Ze^}prg6n(>frjR%_@#?_~Klpx6}-A$=LC)psh#eJ1zN@zB>s>Mo_e-kU~^|!<_t~ zxyRiYj6)|1JC1|IsnyqZPACt39CDs&>)yH6B-5*r>!oUwF|f15&};>0`v$-e5C5qu#iOGY2_%e=j!b>?NBk&ZSGP zZTQom7cTzjo!2=@7{Hd5OKtsqhHD|DWU^bQ*qb^IjTgUJ!Ws+3m$SQ;L~34UQf5pj z+bp!HzbFzI)tdbzx`r|yej$Z~n`Xw=Z}inP8eBJipuz)JyiM(9k;;0{CHwC+EpM@&)i~cc>h~W!WHAoZB`{So|4RD%n)2(OdbwXWmS@i{c zy|uarZJlRJ68*}95;TIRj^E5}gn~j@7n+hfO8qd9Txc-q<)7zA0cCevTDs2uN(r+U3v;a1ph3v6P&4-qI@h6GA(1l?iv4|Ww!^8#e zaEL%oGEL0{!ab5ldM(h65DV4&>)zPDcrbB9a~s8?2B*A8H6f=!k|FeV@EmcsOdcQ* zQXstBTx-m=5%Rpih^zHsQIgjA1d|owf5k|E@9L2XVnD|x;=S> zi!`0y%-J!e{N<#2So-bb02Ind1D5`wSlBIeV(#@!(|ueG+3i?fV~dYA_&SZ2Edx!` zT7t#*X{Pu+uzy2YiQeiqRR((91axWle-DA0R0rIT#gn8h>|8mYt$9haShbNP@S}q3 zp&Q05S(oM~TyM@%=!Lr;uhaR);CL11AP5Y`cHZ>sadL~`%R)qJ(o07SrsH_IAw@@m z(+-OnGl3biwg|FZMc!xx7j=LX0Ig$JJsxGM6TYaT{u*be4HdPxZI0?zG9K(AH0^bN z!=k3P`ST(32FLrjKpob^=oY3ZH8k#VbV^AZThXombsH^v^0cn7#e3@vzWA-N(Pjn< z6+J1B=e$c z22~NJRc`7+>g^t=BcPru-robm=JQdywc99+9V4xQLMR;u=>0g@c-_3GAh6X;34YBea zw=mKsd#BtJwgz8sR@N?E*f6YQ#Q6oY)Xgnw<-Z2)^4-uL0cm6OnI;wmKMB1X&2zjU zXCeX(J2V$$c#;7lk>I3F!l?3%97R1cNJp3^G0^KeR$E%nm1GH0KZD8zK{O+CLkQae)C_cH|yq+`MGHlcl4?phzV7 z9k#pT5%KFkFZx<1U%+RbHn2#EK-1!Ba~vKBjWkdGE^oxR7>^RxO|_o4kk2f(GVF~w zvawti)0j{Et*~x%mc4r>is*ynpWORp87I)RKg&0pv7U%4k??COP^l*V`}kBf;V0Gw z9{3J+c|pK^x6NI5s=jfb{vFm_S`t7jH5Jgab%ukk-7?2qG$c%G(5gO*NkWM>ihXmw zYSC8;1qbM9;)vAlB4;V4J~nnAMqW~JqOPl503GDDPZ=U2Sct6o50lD7ZrN8|VNPC# z9|waX%zai<#&`|P3;+2fX99wu*EvSbw}zDirrU#G%$mct!)MEcz9RETu|FwM9yx6M z+=wO=2I~4uyMLcB>4#T=WKVl#qcTp;M@NfRv zY(tjG8~Jhe9D%eVNWh0%xg5 z%tvVJXa{UBRhE9Kbx=5gu37)2`ClB88+@6x+aEHz@?GX$1ZRJM&;wg@YE;KZ_nqocc$7z_mvc+8Hi)uvoLnBj=L1@_xl^h6)x(TseV&`vGaU zRLfWLagjx_*;3drW{@)Jqz>?wyXaBi)%0ucH%Cg<}*BJflfv)$EC z-SLCf;=x-u8X^qh~2&Z;XTgdop&{@#=Es1f%KN7`b?(-X=9|INWvwR|LHhbSV-r==PzFHXVTJT zd&73YEEJOH0r;l6%?N=G5}uHCG@|o!2z6Z5Mph-V`j2ag96iJ(gk^>S21I zYWOt!#mC2Not(0s&4Y**@|}MM^R(uOVK?nwj>z8oUWG9%7ng3YD9t0e*KdZoC%J*; zTMw_V9QWL&;ETx2ka<+964(noILFRl1hVzIs}-GiOLEG9!9pJHI^hE4+n;b79$~bI zbWIn|BL}JR25!Vru2aNYic5a86=5O-?TZ7C4t8dqh>s%lyqS=)tL>go@1yk7nh}!+ z<8_LA->#6!WwcXxL?aa4f7X|B?|tVhLU0kw`EU9-KmxEA1n}9aElI{x#Z}VTCHrJx ze8<%C8;o_*#AY2{J%X|@&=;w-(X$E5_6;gf=1c8#tzhfdP3tlVw za5a8E&K$dUJ5%Xl@O~YP{#LYRVy-#LFL0bz$!)ObOhE?JrioZaLpM1=RZq;I1jVa- z`tx{`W`c9-pQX3M)sqX{nG<^>3%9(>!keQmHI(1~?DNkL@U}OHn!x1X{PTRXLG&Qz z;x+os7v5D@;isRMyAyY#Sa+L*ky3w2vTYMrIvKO2y~CSral{=4jo-%Fg9P({0Z|zR z%metEDvuyVc*#{Y{korl535i9k_7!vS$N^W1+APIL9Z-mXa_UMI#A|S8YAegKcAZ$ zjZqC>lUFiHZa^$mRlmt&Y1jV0RlOH=v33#0v8etOa)n=t+L8ZEJx4a&!+|2@%GI#N z+0pMW!?2Q+mqK{#2gW4=ERa}sA|vFf_d&8m0jBK9(o&(F(jg5ubd7tgHNQKx)B!i& z9HV;Td(8jxZeOCir~Y>|*R9&$_3B|3_KgQ-;hfsW-hPOiNF_S)+By2&yub7vE2uN4 zMuHI=;;o5^)RAr<%jl{*;~9;Ao|-1&a}xF4btQ){&1dp5)Gr=5mY4MVapEa$T2bus z`=Z)*QSj8Qy3_Mvfqo+{Fwa)$EsRm$e`}~Pw)c*bOT^uxKbn%O<*B!l z)qos%AEXF>ItN!O!XNIOSV5$0nnTYm-nb}iUrE`5pWa5}Ev$-(W;iBQpWU_PMvpO} zCBb73csE2IZp#l6$gFj5t!2}ULurc>16jprMMzfT3PR-e_8#R$*x1zLvL{ z=X|MZ(>ybEbN58}>Qfnln*T^>e|45?LTY;Ws?gT62oI>BmpA{p%$4_gcdbu2`c%A% z-NTxUWG%HEYc8Mc=^9!$n>(({ zE-IqdfNA=anmgG%P<%JLUOSWeIDUc~rw4n90j+}l)=$9* zL_A|$ICp`MpznArF7Uhwp2}#_-;jjGoX((r&<{U&qmm-z#JGgwU_E>!Fd+}RRYN@E)P z^6kU7f)_7tHWgbR92v70d6|ORrIE>`c3qBYEf_)ny754eRRVjy1z&GQs%Cf`t^SD6 zx>Z0i-P3u5Pnf^veE4|*y8EScHwICja1%N|qb+!&Mw~ zzoBUGbTJeO51mu|6brh2J!Ww0w900{jq&-Sm#ST}#ric0>fb(qd!JD11v)#VE05mi zVQs4Q>2l8Q=>x^mUcDzUbQ5>VxDJRW| zm2jRkhhyWDaE}6+}+mTX|s$a#IhCpczAEb1B^U24+ zZl(>a=Y-N3Wx^+ECB`fQ06Z%5Z(iiYSW@q1-70%D)K<}zG`j`7PR4fb-`R*Q75zwl zZ~n}LP|ctM9R>fY_*f2jRUsl%UcUqR-2QVRI|SS)`h|3TVn@#HDarxr`@irZ++o*$iEY!cgHo(bifv&l>$bE5kNPpm4sxS`UJypUeP-?7b;X2Of#KpZ} zuFjYu_~x-vdd{dnFd+eLy%jod4;%Kwc!!aQO{9e3Re?E*m=%%nb=y44Mt6VzmL}Ef zaaAzBE3Q(%!D(mpP7(W3twqTc2Nq_-n(v_JYA`wV5#@W0ikj?LVeRnPk zAs?kHZliy6n$DRFmHN&UAUO!E2wqAA0!sfXM?5bsoeC5tCo06|*Obhi4$g}QmKFxI zS?stUO`<5P4r1XUaZN8#93*(dC8{UOetZ4CZ=tpz@hJ+9nhFSf6Eaz5vc;i|B}EHl zgGm&fDEv{(Zl5mCFN=)lDIx<}e24>;BCwFgs}`LCBQ+P40Z-$nTe|&$xJU{n33b1J zafO8uIa$*^&%4728THDR9*q-wU9nkcxl(_`wT&so{Y)!sxmd+AO7^BV|+489KO zh2tq2Qmn#OKY35sW1Q6A2GnwO8t!Z?Y*X!X{AwT+vzuVp;|o8;;Rb}F^|p0f8YZV8 zy024CMfiTz^WcEB7~B2e?EzwtD+Iwj*V(5L1BMmpb@h^~i&=M-WkutRde7f7vY)mG z>Murg@E?zdZpJN68B~Q4YQco^oM@zo=dgYjrTTxuhPQWTszejrgtt8X*Ely1Zg#2l2&l6F{Q ziYfqKpvI88XPvy>3Gr*s`|<_<@*m%L?R zfUmejvAYtkwH8l!@JaKB>JX@2Y;U9=Gg0?A{0*46U6Rdw|4V~ArxkVIQ z-XZfc;-|Es_{L{(vm-%j*@sAC9=F`WBP!N|qr=j!1 zmjc`&y&6ajsSUcti^c7R+HaN~F&S6uH9QJUe;;~I z5m#T0=j8)mC4xAT^0dO0z+_gE%{FM0PToR#IisCAJ%LzAmz!_fYc{qo?s1dnZY108 zN{kPWv}+>mRP(sPk!|L7pQY(j^qz z{cNo6-c7z3Tal6rz;Nx=H1MnageL}1Q(Mr!>XLNwkuNWxG9q-St5b4nk1L@yic=pz zPyWxY-Ey?KTG;7Q&^G%~hDG!-WADaQL`p#TB;~r3?Hh*PsK3Osz>%MacYY67?wcvq zMxWA*cjh@S`uDO>PIlNRx%8>dHaj&^4`eRuzF&m?C$|1stU!V@O6D2b@N}g9#h3W7 zsU;$>#C#k?Py_*tsOsCR8A{MoaaZ_xEjP*f$rQ)gS~aD=fdnZ9Aj3fkwofp+eY^BZ zO9Sowqh}a`##y_(P~T077N`j3yrCaqj`|=X0Pwfb6cxbV+kG-L(W8ue50zU(fX5oB z*8}3yCPN8Nq&3~44gmBPd_*4K%?u6l*vr2h5S%(9O*HV@cFB#C|8uJM;roHH<^TA2 zw(YoI$sa78KAlA*+wou_ol{Dm3T3A7?3(UZtxB6`FV3w>A2v7m^oPMAk%Z4AuIlYT z)Q>f($WcH&#R;D%caY_F4)g~=YPQVRQ5$)|jt}1s`^y-V|4Y9g3+u0|uf7EMwQ$-E zrTzg0IxISVS%t;b83N${9 zB8AZqvPg$bG!|`@GA}t&$MyH*{R6w}++j7CGK@A1ol&&rKju+l6X;0d`3qeBP7hP3 zxXPQj+SN2u*;SueE02KYk?)F(ARIA#8knj#{3^2>)dcB*kKiDS51^KDi{luGn}hsT zV0mthclE%i9qGRaATr!y=%$tWSKndhIs*~8cCu-jK-}*%j5}3Pe)e2+CvBYx8Tna& zk@@Hnw@_>zCxAmHtA{ZRb^XuIjQm1qOKtIcYWbEsxzY^Nqjeua!&^Hvg6H=VGdihQ zNOj9?z z-mULcJ7#wgkQV@_$z;()24ep8&s^5G@?>DuZ|~g(1Q#1hD1Lg>2exjw;xGmdy@26# zqHy|lbd7Gqy0%}ZQU&!7Pg@=SI9cGl(?xyz%p5fo(}#tEgv#!YE9dSvbyg)UiaAVHaCDZ4A{)u4@8%2{1_fP@OVBEeFATqJS zmoGS?6N|QfKg}kxd->Y?KKSnXdOdVO*BtM!wmpn1mg=FLNughVp6+Xb>|)%#gHLBiSj0 z?7Oj3mWiwl2_;)biR^2P7Q1XAOEE=cA6cIHK7YpZdVW2xbI#{;&bjxV*E#px_Z>o7 zmxj7_VQTDqpeO^OoA0Q+S1CI;930#HZ#T0B2i5u@p?wZ`$@&O!VnX~%F3MMEVm(>L z6OXRM`M+=7{hJ-yS-c+ngAYgj`=LAgMEMB@nBak7lT+=In_PPnMgykj17Edh4$|Xb zi{sZM7mag;?p{S7->fDb51ED}AIwca7}`_GESqi2juTpZW><(N`FHra4sB9I{+N7B zdsT0+|6#gZXmfV?nKTLPO|n4%t1OQY;EN2Zkfb{(Pt~Ip|G9&_==(c@2e7Zz%_M@6 z3ne>1Q+M`gX)|-F`T)%l5CxDWFLAlAI2D#7ij55x9S7Hbu06T>GUUZ?%cJ~^>aAq> z++mmU*|xpM=QL*zmNo_9?5#zMqRjRnvW0@M!W0+@Y0%C@2*zr%;h2s@0>Uj*o6aGu zD8khIkW4LY?HEOiv$n^ZEv&Y8S`R;Dru`@dSW2%z!mI!(=q&w)geXs$|1a08XVwx` zeg5cNP@RJ$c}E1f3S?X$cy!Zr!yZI@b$P${XIw_dL6^;+ zi4;6u^iNHMbW~rO$O0b))ys%M+DooC6dGYyc}p~TC0t*}iK8XPi^EBd4463b<3K6l zC-V5E#1F5geI1(;G^5GW_ppkIJ{ z_%FgDP3CYuVwGNcJawh4kWFd_I)YI zaPkwEZjG3vA221R-x6TXh5b6N(u$U(ZgxyC=kK76G`U!1n~`NYL;DZmuW>{Y4}P{Vi+tcN_?BQR_hK80Je+`Zx zkNlYph=BJdn3|X$^qEZnI71(X&`xPOwpwW0Ky(EXH+8kJ4o_7RZgnse>d_5o`mS1RctG6! z*c@yj>d1hZ*B$gfv&no@g&11lS(|?duv*c_*w$K?HYOhQE_3{mU@TO`p?pa2rr<75 z^udV%fl(W?J00FP^r|+t-zFAN$~-lU4B38lbt{#&{Z*!Cqjt*u_{Q;--_`Fmb~6OB z2e9k zg*?Ves3oRd6mer-baSdZ&7wK!Ty`1+kdE6_33g-*FVy;s=~Myps5xHq+c4~(|Dm7* zJnRza_q)X3mqRyX7|*`~!Fvu7uTIT~(i(b83i&{Sy!P#D;jo%F=5G zx}NRmUy#(mfvjV2INrPe#UxVnV)qv#)IxW{VdB$!tIzU&s$R@BMxLMg0`j@wrUiPQ zAV`Q$mv~B#vN}ee(RPqrT2*!b+Az}45wVP@3KbR>-nD#}x;CSAplp@L5Zq@lY~6f< zSq=9hR_X>kRr`Am1p1zTn=Zc)@RN(CH6_6bS`Ek-_1ODVSAC)0=%+0({b}mS+`kAD z!tE9p6_9)p{fl4uyUDrHmj|Sn@lBfufo9*3rylF`TD9o*(y-itZ<)eIT=ddzRR-Zf z-y5Zao@_pAZVn>VtNa#^wW4P4*AKkxfi^QvmKMz9_tiIUU(O`}8(I5S(8b%PmiRWA zHk<^9yXC(#|I!?H84q#QPoiaMWtP%EQU2&R5*0yV<6TlWhxNvI+ z&OV+f+$oH3bX8zyU!i+bFbddkV?&BwTp$@8Xa=B4KtAoe)LhT_`&_OB4k`aZn-nD7 z_85n0dCGR`cGBDS1peL{!Wfimsz>(6B0UQ#&(2!iu4{sVj z_gBhWf*dCD!IK`0nwVXM*7ZeLrARcB(xeu>a{-`k+FROO&6#-Uk1VfA9Gei`5TYEr zG)Wf`%!Wjs>=C-NUxzH52%NHGU})cyr(jHp2(hnS*U4UN>UJuSN%vV79`cv%P&2h? z(RCw|YC-Y|woU~EB1X&OLl4`1J&JPoDnp_fJ>j#SPpeXYwyp0m2g{cDN049_HVnCn z?CENM%Tw@lwI)Y}0kU}))FAgaV42Vb!5XXRHa;Zpy&12q)@Oza*7*ke^1J_e;miZc ztOwfms|^LUg|pfYs;NhF*h>a~3u(cpJ?bdjk_2Y96LgpPw0C?+hT6K+&xFdzR8|(C zR4No3&&&KQqtBX+$$70g94wg1cn&RbQhoR3XkJjfL#Nwd3ecM9NY7Dw;iv`|f7QRZ zp#LPRVG6aI#xnTBx!RVaBALRDekyDq!?XU}T_|5kmDM7iX@GGnF7N$L~=n?Q=omZrSi5lZ9;C9%g{BXF`nX2HkbA)5zOy(^uZ!-7OF~iWK~u@Qks~ zEXA?8C+#w~6G609J9D;X?cfCR6R zaBombHlZcT^|_c%Pv-X$pnOP*P4m0ldit^fxqz9iK3`Y)D|J=yU zNJ2*y@Quvh|Hyp{((=SM9C&SJmH%1xQr<;20Y;RJVDyp@=iHnvlyE}_VzjKfF!uR;&;{BlC&zDb9& zHo%SNx*+uHGl@|>?CNpNbHE+^62_m-k|qe z2Oi1-+BX>f=w9n;9G$$LLN*|_c3f}ksff1GkX#jK{H}OQas9!NOBylt^3XKpFezVOiWtiiy79vdeG~xJ?`mb96_lgm~JX^x2wQ@ zJ+idFO6=kTuR z?k}!{Cg2>-+zOX0Wzy7zr^v?jW~5xlG81_r0ytgPRO9A6EcpJ!)*^W$NberAKqy^7 zm1Vf5ebB`#q`vM$l)%`nb_Z?^U?5Xa{#g<%@XP&`x){sbNwEF{npvM>`daRiusksP zNyU^q;MplJUD{sbwqd0^u2a16DmO$n}>kPcf#)WH|zv071}EqK1+9pn+=6cNM)$ z=(Gp{GEfrZr}?~@gE!@ltB@k139ZDApzGbRD2Nb}s!dRI`M#N{2P#Ro^#rsl24^@s zk>-S~>XK}za1TdBJ!s!30Nc2IT`8+3nw&BFheb&;&GtUfnjyC8xotxhZg}l~uCCO9 z_N(znR414z;XN2HIk^^_6}NrPOW||kml#JTXS9Z=I3qDnuhLL9ZhM0IaCA64VDx|) zai&lNa%cs{Ckg4LvQbBeM{a4=D;17(up`WTN+SCT>J5kmBFS`Yr@G*E&9wjMM{JzT zn!~;B)P5Q9sx$v^@D}?(Um01xjCL=a_*n$Qe- z#K1z}&&b9ydGsP@0xH3*X>)mxX+z&moe1x_pBWRYdlo@iC%*s~5I@*vr0})Yj*cPj z3tj626yTmg*yH;eq3Hgpwz;)%ehk2vP|M-)Kpp>$_E3yV7LltCxn~@bl5*$1R=xZ8 z9~^TY0VZ&HeAHJ~TZhA`@-+M&Dfv1ETwk zu{oKeSR4#4IYZO?V{YA|@p4oD)3xgt9@12qhyue+*wy-&yOoY>{T~7K!Z#uQH>oyR zUch+;bq01!9)IIiXr_OqfUKkYM9$64!p^U0?w(u4iJBOo!)Wl8P{N~cYdUJ({B!Y5A-iIc5W*sqf6nF+XY4m|2%^d*h=@>o z0bOhRqDw6_Hkw5V?I6%>v>gc1d{c}n%*wk)rR)Otu5%N+%0F=bK76&5Er`kcVI_q( zo#_){Otd20?M`rx7^E-dwWTUc!-TvEeHIY!`sc}4vNSAMfB=vuuD!}eq_c=)M0*m< zBFHgNO#3+5k8dgORq5XQqd|>;nbG5{@Aa-?ZtAC+kmUyAG6U~ZxIZ19H$8RbvZ6wZU6I(bvJsk{xBqf=IO69cSsR*|JUrRI+*-fdhYL})CcU^z z`W;ZauB(ntW{dC#l~_m3&PsnKD%{X5wEAKV05&NBzptKoLl za&2+8ewQ#B#u`D(rkgQpO29k|)RmRDC16`$%E$6PisSHBOO;i%gPjtlU(|QA?pA&q zyp()6%Lt3N?<~YT|H59;S=9Zuyh7{0?L@UGU;9}Swu#wkw8fP zlwDL<+mpF7|7%gfLeN;?EH&}e+L%x%xQXxbvL1&VW-){FhOQ=;G z6{<_M-SFjcFL^6V0TrPhYtW$r;@=6!H4veg1K8aZYvbwQfwC%MK8GJc?hB9T>n(pb zRZZV?Rsurq{5HpdDHHXP)gi{Pt?#Oj2HvX5fiq>my%8KPIiFJV)*vLI*|tr1^oYk> zT7zKsYEY~-RZ}sx_EXlq`1oE)s*}`2zY)it?&A-c{voKTixR3xTZg^>hbGK&NP#nHO179qC|#X*7{@u7i!E)vMnhe%6}%Vze0o+AmqyzNQ4NetR$LMkzz~*D zr+P{7*LQs%+g}UB6;1_L7odW_KFgkpARPkG5D1rf4Slv}@|xc3XDci^0VMUQ#cB6s zuHDCQ0BH}aK+1ix97(mgws^awYkc+kL|F zg+IrMO_11QMIg#_K}v^IFi-)PoDqu{AE$S7sDIS6(#MN~cmG@tBS%6p-jY(i&C(^N zKcL;F>eIe&{^1jrXIjO?W@fasf|~FhlCdI?+u5qC$e97H2r+o$2nq=p+N%4~q<;7j zL^&m0SL)2Uqd)&;yEyTtIw3jTJ=Tqv*K&6x&9GbyKqeoV@6?*>bk0W~&31J1MvMba z!f{oCCM|We#xO_50ZF|M~E6t24 zHU@&6x{&5&o!+Q+jj!7s77ELCZ^Ix6#jKi#FsANS`*cU6>ds%V84KWze!<5Uaos06 zMMLah!@aPv!JGJSXKbG=}owyzat7{KqW4twR0kX|A?qW z?w4>NCl)Ghyb$wK5uj9?rV@?j1dAr#U)h2IMwBx*#nf;}+Sh6J(kF|>(HVRoJEP#Z zUzB?soGvTpAGLr#u48X-wIP9XWre1}`>UFnQ6GpMiyy0>EvVD6F#62k*p%bX3h}qE zD~F%bhkWn_yjxS9Pw6frU8F1OZ5RFDhCqO`<2A?^vK3q3i2d#^J8Xc|=)antQgQL> zc+z-o*kC9GFgIj-7jIgOZmu_e_w3h5NLi-nZ3ZB#)=<3WW##p=&NisP+w0C*HxB{8 znwH1oz>)99Z87giw%vjW{P>RwQ-7h~W!~&V(_Pq=TJ$zdX5p zPC+7nnK@pxm2m+K-0piIEUs_cs9Qp>6c>%3UHS_GPG82qSiCW@A|hlw;A8%;a51-c zqLrJ@++5i5?p%Wma-v{3sdoea?CG-g$2u&qVHEs)ewb2*Jz16z?C0L&ZU`&N6t!Xi z3Vf#0Gf{$6xc2&hQ_0)CZ}Nd#sSiGg=CS~@z9t+7iH{%8`?eIU2~f4rm%i)ybsNH+ zpJ)T^a1}Prby2vw>ZOU_emXwS>jQ&=oVvS~yQW~kejlwLS|KHrl0LuH($bxH_G?O{xd<8Q5=rjObNy6&^UB$IzLK!o_Xd7|Mxv*0*p`00rmAlMv zo=^M|fFOydTabFd*A#qi)iDsDit>~ye6Vv%ky1jmI^Yz&dH8K%^oia>3Ku}RL{o2o zWk%@Z?r6K4(-B`?mX@>3o`mFyerQm?%nUd^+8m`#5?QRd)|rA_L2si%F+*q z=tm=sq5WORd z_;%BhvJD38WPAC=<3w#4fa~Xr&z;p(*vtv11G#4kF7+%um}CAw1}6Of=KLS>K;qsB YIBYneJFi=c`#-Y`^o%dR*0GEJA29e|WdHyG literal 0 HcmV?d00001 diff --git a/public/default-icon.png b/public/default-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc5a4ffa8cbfb91683884806bbb820cfa787d25 GIT binary patch literal 29737 zcmeHQYgALm7CuQ3A-rOXqSPfZ5m6)$P!wW_Jfaa>kWzURio6oY5lCnfB~Ttwu;q%V z7p|x+Ha_tIRWzU?+5#ei+*`m>VF?2AP`n_BE(I0gCO{!g?7#lFKQ>vc%sDf&&&)n^ z_WsT{vnT)X^5hud&G86=7`bm`dn1TWq3T~xS9zvyv7%0SV2rxCc}2N#+=S6W(HF6y z;XDpcz!Rb2-W*p1amdW{3F0sGF=hQwqFAxy*VM+mye#7}`+pThdpD##=pExJVp?so zql;V))bu|*iY{H*HF^qN&N6i&l%MU$?idd&w_3a{|5*m*M3L7Coz!I`^=~=H@0scM zm8?{hW?zfCS=;*NaqeL9lQ!`{o56u=Hi0>9mRK9}3zVMUIu&;wS5`74f|9~b8lY`lIgg-tJZG{ z8_};^8=Xt+{ODFO;y3?CljoP~j{MUuJ$Sv%vh#b#DqGTLKOw&k(3vfM-m;HrfA**4 zq}ug0tMkA2B>#3Z^wZJo=6x_vvnxq+g_>ira*7 zUJ0r{L`H&2Z|oM~hgg;2KHuC}*ef}AeoaH!iZhKzA_?uO((UEUyHx}1(PG=+T>SQA z?_9&jCX0}cPHReG!Z3MJp$BT6UYqI1Uw!9A`pC7%^CGZG1C&UV(NynvI%1ED%|J%i zi#fh$Se1U~emvy*KA3XVw93-y+pW2Vl4|<)(N}MG_#Hi>*O}#LW?;V8lIB}Hy!5{Ksboo{`8JBA`a$DSQQde& z@RO#(;3sPW498|C<5K!se6QN~t@^hwKIytJu#)rA$&<@EP4S5iRh&HWrM>T4tVJ!7 zR3a_ai_{Gq?H;aKmlahroMo{zrK_!*_2^fzTqOUxT~-~`K@|myg1@#9%Q@Q`;|Eto z4O~2V3I7;9^m(h?HK3WoT*lq)RB@|<`|0N1x!8P<+<_&xPjx!-2#otrAp zq^UHern$-6GI3UUUs!l1ZTcRw@cNNLHw1A;+}SQ$q?rC@7b9dbL8rWHbgdgUKyS&} zUd~Uu-2d})M8AFA6FmaaS%zS+Ix`9OtT8_I(#-L62_^=Y!_g{E*k~}n@ZKvuj)oWl zF{JDTPEGEV92A4|y}sy?wtPeXWGxZSL`q26rY7>jKebRLNrtI4nQqy1;?yid|ECFQ zWX(Pi=pk>kL?AnhWSA8}`(w2mscW-NO9b)2JbK9KzB&8;tS;rX%Va&Xwfl1qt6Ly( zs+gmtvH69gS|Vi50cEm-7dQ^;cVeD5ukKXI*fEMFJN9YZ8z9-9dM-JNCCY7 zWYhqefYSk|!^;u8k-{6P#=AN|Mu3dKy#V0_ETbm>3Xl;XBS1!gi~tz{GHT920m}$1 zBe0CXG6Kt}F=Y>s5g;Q#Mu3a}838h?YXDYwVPRPvtYuVxBqp&Vu#5m10Wtz)1jq=G zQPVpB%LptZu#CVm0?P<2qq+tl&d7yt+jR`LJcDiUf5M%d5X S@Gh#~-E&{>$-dwkvg>~kkEGoI literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..10b730f9c195067bb6b0cc727a3802d25c173bf7 GIT binary patch literal 590 zcmWIYbaQiKVqge&bqWXzu<)@2vi0{LelMV|AfWK)ddJ6PXNQ0%|1+2v57jSW5PoLQ z@F6^iVd2KNy8<<0xRxbw_1mvm{n~%m#5Y$vU;JmW`62)Ras8kE{~zuD9siHQ`Tyqh zk@_F+*DZc=Tchj$hx>coE*iO?ywiFn?~(nV^7RuVn~jZjYZlA7{!W0`pU@mAUWi`sRBIWQ^RRjNF=ea~}h>@=uJ^|INL_=%f6<-SHYx>wAHQ z$GWHeodVL8Es}dV?bephTUK1&X1!|W-6@?wSy{)6+rC8U{X1N5E;G}1*2<;EscCma z-O_(GG|!w6KKxdTA(XFf607a_@6Ho?0}NVH>aOFvpY=s zPh9G@jG2pLz>d~GRny+JE}+@?uFmDcxp`ofD#fC)tNo)dp4lTh_qc3mbM%%oqIdGN zKweQX{rS>@S#vQ!tU)H+D=TG~8Kcn!Am01UUbpP_@KJJ@c2zJk;)YP + + + + + + + + + + + + + diff --git a/server/.env b/server/.env new file mode 100644 index 0000000..06de8c2 --- /dev/null +++ b/server/.env @@ -0,0 +1,37 @@ +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema + +# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings +NODE_ENV="production" +RECORDER="active" + +SERVER_PORT=3333 +WSS_PORT=3334 + +POSTGRES_USER="postgres" +POSTGRES_PASSWORD="postgres" +POSTGRES_HOST="postgres" +POSTGRES_PORT=5432 +POSTGRES_DB="eplace" +POSTGRES_SCHEMA="public" +DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?schema=${POSTGRES_SCHEMA}" + +REDIS_HOST="redis" +REDIS_PORT=6379 + +PUBLIC_API_URL="http://localhost:3333/api" + +JWKS_URI="https://cri.epita.fr/jwks" +# DO NOT ERASE THIS UID, IT IS USE AS DEFAULT USER TO SETUP ROOMs, PIXELS +# CHECK README FOR MORE DETAILS +# You can add more admin uids by separating them with a comma +# Example: 9361,9362,9363 +ADMIN_UID_LIST="9361" + +RATE_LIMITS_CONFIG_PATH="./config/rate-limits.config.json" +ROOMS_CONFIG_PATH="./config/rooms.config.json" + +DISABLE_ADMIN_PREVILEGES="true" +DISABLE_AUTH="false" +DISABLE_RATE_LIMITING="true" diff --git a/server/config/default-canvas-250.txt b/server/config/default-canvas-250.txt new file mode 100644 index 0000000..9e52292 --- /dev/null +++ b/server/config/default-canvas-250.txt @@ -0,0 +1 @@ +B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B cB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„CÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BcÆ1ŒcÆ!!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!†1ŒcÆ1ŒcÆ1ŒcÆ!!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„"Æ1ŒcÆ1ŒcÆ1ŒcÆ1Œc‚„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„CÆ1ŒcÆ1ŒcÆ1ŒcÆ1ŒcÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1ŒcÆ1Œb‚„"„1ŒcÆ1ŒcÆ „!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1ŒcÆ „!B„!B„!D1ŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1Œc‚„!B„C„!B„!Æ1ŒcÆ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1ŒcB„!B„#Æ1ˆ!B„!BˆcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ1ŒbB„!B„!Æ1ŒcB„!B„!F1ŒcÆ „!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„CÆ1ŒaB„!B„!Æ1ŒcÆ„!B„!B„CÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ1ŒaB„!B„!B1ŒaÆ1ˆ!B„!B„!D1ŒcÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„"Æ1ŒbB„!B„!BŒcBŒcB„!B„!B„cÆ1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒbB„!B„!B„!„c„!B„!B„!F1Œc‚„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcB„!B„!B„!B„!Æ„!B„!B„!BˆcÆ1„!B„!B„!BˆBB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcÆ0„!B„!BˆcÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!BŒcÆ1ŒbB„!BˆcÆ1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„"Æ1Œc„!BŒcÄ„!B„!B„!B„!B„!B„!B„"Æ1Œc„!B„cÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcÄ„!B„cÆ1Œ!B„!B„!B„!B„!B1„!B„!B1ŒcÆ1!B„#Æ1ŒcÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcÆ „!B„"Æ1ŒcB„!B„!B„!B„!F1ŒcB„!B„cÆ1ŒAB„"Æ1ŒaÆ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcÆ1!B„!Æ1ˆCÆ„!B„!B„!B„!B1ŒcÆ0„!B„!Æ1ŒcB„!Æ1Œb1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ1ˆ!B„!B1ŒaF0„!B„!B„!B„!BŒcÆ1Œ!B„!BŒcÄ„!†1ŒcŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒAB„!BŒcB„!B„!B„!B„!B„cÆ1ŒcB„!B„#Æ1„!D1Œcc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1ŒbB„!B„#Æ„!B„!B„!B„!B„!Æ0„#Æ„!B„!F1ŒcB!Œc€Æ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„!B„!Æ1„!B„!B„!B„!B„!B„!Æ1„!B„!BŒcÄŒc†1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒc„!B„!BŒ!B„!B„!B„!B„!B„!B1ŒaB„!B„!Æ1„cÆ 1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ„!B„!B„!B„!B„!B„!B„!B„!BŒcB„!B„!D1ŒcÆ1D1Œc‚„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ0„!B„!B„!B„!B„!B„!B„!B„!B„!„!B„!BŒcÆ1Œ1ŒcÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ˆ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œa!ŒcÆ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!†1ŒaB„!B„!B„!B„!B„!B„!F1ŒcB„!B„!B„!B„!B1ŒcŒcÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„!Æ1Œ!B„!B„!B„!B„!Æ1ŒcÆ0„!B„!B„!B„!BˆcÄ#Æ cB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B c‚„!Æ1Œc„!B„!B„!B„!F1ŒcÆ1ŒaB„!B„!B„!B„!Æ1„† cÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ„!Æ1ŒcÆ1„!B„!B„!B„!B1ŒcÆ1Œc„!B„!B„!B„!B1ŒcÆ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ0„!F1ŒcÆ1ŒcB„!B„!B„!BŒcÆ1ŒcÆ1„!B„!B„!B„!BŒcÂF1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!BŒcÆ1ˆCÆ„!B„!B„!B„cÂŒcÆ1ŒaB„!B„!B„!B„!Æ1€ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1ŒaBŒcÆ1Œ F1„!B„!B„!B„#Æ Æ1Œc„!B„!B„!B„!F1Œac„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcB„#Æ1ŒcŒaB„!B„!B„!Æ1„1ŒcÆ0„!B„!BŒcÆ„!BŒc€Æ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Bˆc„!Æ1ŒcÂc„!B„!B„!B1ŒbŒcÆ1Œ!B„!BŒcÆ1ŒaB„#Æ0€Æ1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ0„!B1ŒcÆ1„#Æ0„!B„!B„!BŒcÄ cÆ1ŒcB„!BŒcÆ1Œc„!F1Œ@ŒcÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1„!BŒcÆ1ŒcÆ1Œ!B„!B„!B„#Æ1ŒcÆ1Œc„!BŒcÆ1ŒcÆ0„!BŒc#Æ1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1ŒaB„#Æ1ŒcÆ1ŒcB„!B„!B„!Æ1ŒcÆ1ŒcÆ0„!B„cÆ1ŒAÆ1ŒaB„CÆ1Œc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcB„!Æ1ŒcÆ1ŒcB„!B„!B„!B1ŒcÆ1ŒcÆ1Œ!B„#Æ1ŒAŒcB„!Æ1ˆcÆ1ˆ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Bˆc„!B1ŒcÆ1ŒcÆ„!B„!B„!B„cÆ1ŒcÆ1ŒcB„#Æ1ŒaCÆ„!B1Œc!Æ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ0„!B„cÆ1ŒcÆ1„!B„!B„!B„!Æ1ŒcÆ1ŒcB„!Æ1ŒbÆ1Œ!Bˆc„!F1ŒcÆ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!B„!Æ1ŒcÆ1Œ!B„!B„!B„!B1ŒcÆ1Œc„!F1ŒcBÆ1ŒcB„!Æ1„!Æ1ŒcÆ1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!D1ŒaB„!B1ŒcÆ1ŒaBŒaB„!Æ1„!B„cÆ1ŒcÆ0„!BŒc@CÆ1ŒcÆ„!F1ŒaB1ŒcÆ1ŒcÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B cB„!B„cÆ1ŒcBŒcÆ„!Æ1ŒcB„!Æ1ŒcÆ0„!B„c€Æ1ŒcÆ1„!BŒcBŒcÆ0„#Æ1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ„!B„!F1ŒcBŒcÆ1Œ!F1ŒcÆ0„!BŒcÆ1„!B„!B1ŒcÆ1ŒaB„#Æ0„!Æ„!F1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1„!B„!B„!B„cÆ1ŒcBŒcÆ1ŒaB„!B„!B„!B!ŒcB„!Æ1Œ!B„!B„cÆ „!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1Œ!B„!B„!B„!Æ1ŒcÆ1ŒcÆ1ŒcB„!B„!B„!B€cÆ„!B1ŒcB„!B„!Æ1ˆ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B!ŒcB„!BF1ŒcÆ1ŒcÆ1ŒcÆ€„!B„ B€Æ1„!BŒcÆ1ŒaBŒcÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Bˆc„!BŒcÆ1ŒcÆ1ŒcÆ1„B„!B1ŒcÆ1ŒaB„#Æ1ŒcÆ1ŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ0„!B€cÆ1ŒcÆ1ŒcÆ1Œa!F1Œ ŒcÆ1ŒcB„!F1ŒcÆ1ŒcÆ1Œc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!B€Æ1ŒcÆ1ŒcÆ1ŒcBŒc@CÆ1ŒcÆ„!BŒcÆ1ŒcÆ1ŒcÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„1ŒcÆ1ŒcÆ1Œc„cÆ1B! cÆ1„!B„cÄ!ŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒc„ ŒcÆ1ŒcÆ1ŒcÆ0Æ1ŒcÆ1ŒaB„!Æ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ0„!€Æ1ŒcÆ1ŒcÆ1„ŒcÆ 1ŒcB„!F1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ1„!@Œc1ŒcÆ1ŒcÆ1Œ #Æ1ŒaŒc„!BŒcÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒaBcÂcÆ1ŒcÆ1Œ F1ŒcÆ! cÆ0„!B„cÆ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1ŒcB€Æ0F1ŒcÆ1ŒaŒcÆ1ŒcÆ1„!B„!Æ1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B!ŒcÆ„F1ˆ#Æ1Œ!!Æ1ŒcÆ1ŒaF0„!F1Œc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ1„ Œb€B1ŒcÆ1ŒcB1ŒaBŒcÆ „!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒacÄ!Œ@„cÆ1ŒcÂŒc„cÆ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1Œc@Æ1Œc!Æ1ŒcÆ„#Æ0„!Æ1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆB1ŒcÆBŒcÆ0„!Æ1ŒaF1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1„„cÆ €„!Æ0„!BŒcBŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒaB€!B„!Æ1ŒcÆ„cÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒc€B„!F1ŒcÆ1„!Æ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ  €„„!BŒcÆ1ŒaF1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!†1Œ!Œ`1Œ!B„#Æ1ŒcB1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B!Œc c cB„!F1ŒcBŒc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÂÆC„!B„#B„#Æ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ0€Æ1„Ä1„Æ0€B„!B„!Æ1ˆ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1Œ@1ŒcÆ1„ŒcÆ1Œ „!B„!B1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B ccÆ1ŒaCÆ1Œc!B„!BŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„CÆÆ1ŒcÆ1Œc@B„!B€CÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ˆ1Œc@ŒcÂ!BÆ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1Œc„!BB€F1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÂ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1€b† Œc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!F1ŒaÄ1Œ cÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B cBÆ1€ŒcÆ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ0€1Œ ÆF1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!ŒcÆ1„1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcBcĈA CB1ŒaŒc„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ€Æ1ŒcÀÄ#€Æ1Œc@„cÆ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!1ŒcÆ0†1„Æ0F1ŒcÂ!Æ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„cÆ1„!ŒbF1Œ cÆ0€!Æ1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ„F1„cÆ1ŒcÆ0„B1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!„ Æ1Œc@„BŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB1ŒcÂBŒcÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ„ #Æ€„#Æ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!@B„„!Æ1ˆ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„€„!Æ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ„!!F1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!B!BŒc‚„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„!†0€Æ0!BŒcÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ0„!B1Œ`1Œ !B„cÆ0„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1Œ!B„ cŒc!B„#Æ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcB„!BÄBD#Ä!B„!Æ1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcÆ0„!B„ Æ1„#Æ1ŒAÆ0€!B„!Æ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ1ŒcB„!B1ŒcÆ1€ cÆ1Œ!B„!F1ŒcÄ„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcÆ„!B„cÆ1Œ #Æ1ŒcB„!B1ŒcÆ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B1ŒcÆ1Œ!B„!Æ1ŒaF1ŒcB„!BŒcÆ1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ1ŒcB„!BŒaB„cB„!BŒcÆ1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcÆ1„!B„!B„!BB„!B„!BŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcB„!B„!B„!B„!B„!B„!B„!BŒcÆ1„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcÆ0„!B„!B„!B„!B„!B„!B„!B„cÆ1Œ!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„#Æ1ŒaB„!B„!B„!B„!B„!B„!B„cÆ1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!D1ŒcÆ„!B„!B„!B„!B„!B„!B„cÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ1ŒaB„!B„!B„!B„!B„!B„cÆ1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!†1ŒcÆ„!B„!B„!B„!B„!B„cÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1ŒcB„!B„!B„!B„!BŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcÆ1„!B„!B„!B„!BŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BŒcÆ1ŒcÆ„!B„!B„!F1ŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!†1ŒcÆ1ŒcÆ0„!BŒcÆ1ŒcÆ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!BˆcÆ1ŒcÆ1ŒcÆ1ŒcÆ1ŒcÆ1ŒbB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!D1ŒcÆ1ŒcÆ1ŒcÆ1ŒcÆ1ŒAB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcÆ1ŒcÆ1ŒcÆ1!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„"Æ1ŒcÆ1Œc„„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„cÆ1ŒaB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!Æ1ŒcB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B cB„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B„!B \ No newline at end of file diff --git a/server/config/default-canvas-50.txt b/server/config/default-canvas-50.txt new file mode 100644 index 0000000000000000000000000000000000000000..ff61da9f583c6e8ddaa8ae0e80ffe6bb1f87b53e GIT binary patch literal 1563 zcmeHFJ8r`;45bSX8az-NM=QF3eS^Rbd=Fa}DGDL*phFc#;B%GqFaD)Vmk!Y&2zumu zj~`W_H|l-;>mF3uFEhg4C`Vr}#)uE}P8Fs-El|(_uuO0asZ!s_Za8(z+6MPqlE;RI zzYbfcW~y08Cst5P9cz$3lu*-y|LC4`NRcIT+GOZLRUVAuOX9+16nZ-7b#PQ=fpvAT z)U%iM7K>Uh1e{R{RlP!fVja4 z`T9tKtxXjz1R({8T85(lP?)W8^cRX5n@+m#3%5i|t`>Juuv_D-p_9R!)CT5cEi7H} zQ^1&O+gszV@vb!jm-1RXFE5?clVpC}7bzb{(if=sb3Co{+>!ado8^hgeaGJ+hduav GOMU>Z?m4Rf literal 0 HcmV?d00001 diff --git a/server/config/rate-limits.config.json b/server/config/rate-limits.config.json new file mode 100644 index 0000000..aeb1cd2 --- /dev/null +++ b/server/config/rate-limits.config.json @@ -0,0 +1,54 @@ +{ + "testsLimiter": { + "limit": 10, + "interval": 1 + }, + "getCanvasLimiter": { + "limit": 10, + "interval": 1 + }, + "getPixelLimiter": { + "limit": 10, + "interval": 1 + }, + "placePixelLimiter": { + "limit": 1, + "interval": 30 + }, + "getRoomsLimiter": { + "limit": 10, + "interval": 1 + }, + "getRoomConfigLimiter": { + "limit": 10, + "interval": 1 + }, + "createRoomLimiter": { + "limit": 1, + "interval": 300 + }, + "updateRoomLimiter": { + "limit": 1, + "interval": 1 + }, + "deleteRoomLimiter": { + "limit": 2, + "interval": 1 + }, + "getStudentLimiter": { + "limit": 10, + "interval": 1 + }, + "updateStudentLimiter": { + "limit": 1, + "interval": 1 + }, + "sendMessageLimiter": { + "limit": 1, + "interval": 1 + }, + "reportRoomLimiter": { + "limit": 1, + "interval": 5 + } +} diff --git a/server/config/rooms.config.json b/server/config/rooms.config.json new file mode 100644 index 0000000..833f515 --- /dev/null +++ b/server/config/rooms.config.json @@ -0,0 +1,46 @@ +{ + "maxRoomsCreatedPerUser": 3, + "rooms": { + "default": { + "metadata": { + "name": "default room", + "slug": "default", + "description": "default room", + "canvasDimensions": 50, + "iconURL": "https://media.tenor.com/XUHq8pN_maQAAAAi/puffer-fish-fish.gif", + "isPublic": false + }, + "settings": { + "roomColors": "#ffffff,#d4d7d9,#898d90,#515252,#000000,#fe4500,#fea800,#fed634,#01a268,#7eed56,#2350a4,#3690ea,#51e9f4,#811f9f,#b44bc0,#ff99aa,#9c6925", + "defaultCanvas": "config/default-canvas-50.txt" + } + }, + "epi-place": { + "metadata": { + "name": "epi/place", + "slug": "epi-place", + "description": "Le Roi de la malice est passé par là", + "canvasDimensions": 250, + "iconURL": "https://media.tenor.com/XUHq8pN_maQAAAAi/puffer-fish-fish.gif", + "isPublic": true + }, + "settings": { + "roomColors": "#ffffff,#d4d7d9,#898d90,#515252,#000000,#6c001a,#be0039,#fe4500,#fea800,#fed634,#fff8b8,#01a268,#00cc78,#7eed56,#02756f,#019eaa,#00ccbf,#2350a4,#3690ea,#51e9f4,#493ac1,#6a5cff,#94b3ff,#811f9f,#b44bc0,#e4aaff,#de107f,#ff3981,#ff99aa,#6d482f,#9c6925,#ffb470,#811f9f,#000000", + "defaultCanvas": "config/default-canvas-250.txt" + } + }, + "test": { + "metadata": { + "name": "Test Room", + "description": "A room small enough to test things out", + "canvasDimensions": 10, + "iconURL": "https://media.tenor.com/XUHq8pN_maQAAAAi/puffer-fish-fish.gif", + "slug": "test", + "isPublic": true + }, + "settings": { + "roomColors": "#ffffff,#d4d7d9,#898d90,#515252,#000000,#fe4500,#fea800,#fed634,#01a268,#7eed56,#2350a4,#3690ea,#51e9f4,#811f9f,#b44bc0,#ff99aa,#9c6925" + } + } + } +} \ No newline at end of file diff --git a/server/docker-compose.yml b/server/docker-compose.yml new file mode 100644 index 0000000..f0419b9 --- /dev/null +++ b/server/docker-compose.yml @@ -0,0 +1,56 @@ +version: '3.9' +services: + postgres: + image: registry.cri.epita.fr/ing/assistants/public/registry/postgres:15.2-alpine + container_name: postgres + restart: always + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: eplace + expose: + - 5432 + volumes: + - postgres-data:/var/lib/postgresql/data + # Proper docker-compose would use named networks + # networks: + # - postgres-network + redis: + image: registry.cri.epita.fr/ing/assistants/public/registry/redis:7.0.9-alpine + container_name: redis + restart: always + expose: + - 6379 + volumes: + - redis-data:/data + # Proper docker-compose would use named networks + # networks: + # - redis-network + eplace: + image: registry.cri.epita.fr/ing/assistants/public/registry/eplace:latest + container_name: eplace + restart: always + environment: + NODE_ENV: production + volumes: + - ./config:/usr/src/app/config + - type: 'bind' + source: './.env' + target: '/usr/src/app/.env' + ports: + - 3000:3000 + - 3333:3333 + # Proper docker-compose would use named networks + # networks: + # - postgres-network + # - redis-network + depends_on: + - postgres + - redis +volumes: + postgres-data: + redis-data: + # Proper docker-compose would use named networks + # networks: + # postgres-network: + # redis-network: diff --git a/server/openapi/openapi.json b/server/openapi/openapi.json new file mode 100644 index 0000000..f3eb4bf --- /dev/null +++ b/server/openapi/openapi.json @@ -0,0 +1,5503 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "E/PLACE API", + "description": "Publicly available API for E/PLACE", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:3333/api" + } + ], + "paths": { + "/status": { + "get": { + "operationId": "status", + "summary": "Get the status of the API", + "description": "Get the status of the API", + "tags": [ + "Misc" + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/reload-config": { + "put": { + "operationId": "reloadConfig", + "summary": "Reload the config", + "description": "Reload the config", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reloadLocally": { + "type": "boolean" + } + }, + "required": [ + "reloadLocally" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/update-event/{slug}": { + "post": { + "operationId": "changeEvent", + "summary": "Update the event of a room", + "description": "Update the event of a room", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "event": { + "type": "string", + "enum": [ + null, + "EVEN_OR_ODD", + "EVEN_OR_ODD_DEFAULT_CANVAS", + "INITIAL_DEFAULT_CANVAS", + "RANDOM", + "GROUPS", + "VOID", + "RESET" + ], + "nullable": true, + "default": null + }, + "radius": { + "type": "number" + } + }, + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/update-rate-limits/{slug}": { + "post": { + "operationId": "updateRateLimits", + "summary": "Update the rate limits", + "description": "Update the rate limits for the API", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rateLimitName": { + "type": "string", + "enum": [ + "testsLimiter", + "getCanvasLimiter", + "getPixelLimiter", + "placePixelLimiter", + "getRoomsLimiter", + "getRoomConfigLimiter", + "createRoomLimiter", + "updateRoomLimiter", + "deleteRoomLimiter", + "getStudentLimiter", + "updateStudentLimiter", + "sendMessageLimiter", + "reportRoomLimiter" + ] + }, + "limit": { + "type": "number", + "minimum": 1 + }, + "interval": { + "type": "number", + "minimum": 0 + } + }, + "required": [ + "rateLimitName", + "limit", + "interval" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/tests/error": { + "get": { + "operationId": "tests-error", + "summary": "Return a server error", + "description": "Always return 500 Internal Server Error", + "tags": [ + "Tests" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/tests/invalid-token": { + "post": { + "operationId": "tests-invalid-token", + "summary": "Return an invalid token error", + "description": "Always return 401 Unauthorized \"Invalid token\"", + "tags": [ + "Tests" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/tests/expired-token": { + "post": { + "operationId": "tests-expired-token", + "summary": "Return a token expired error", + "description": "Always return 401 Unauthorized \"Token expired\"", + "tags": [ + "Tests" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {}, + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/tests/valid": { + "get": { + "operationId": "tests-valid", + "summary": "Return a valid response", + "description": "Always return a valid response", + "tags": [ + "Tests" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/tests/too-many-requests": { + "get": { + "operationId": "tests-too-many-requests", + "summary": "Return a too many requests error", + "description": "Always return 429 Too Many Requests", + "tags": [ + "Tests" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/students": { + "get": { + "operationId": "students-getStudents", + "summary": "Get all students", + "description": "Get all students", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uid": { + "type": "number" + }, + "login": { + "type": "string" + } + }, + "required": [ + "uid", + "login" + ], + "additionalProperties": false + }, + "description": "An array of students with their UID and login" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/students/{id}": { + "get": { + "operationId": "students-getStudent", + "summary": "Get a student by UID or login", + "description": "Get a student by UID or login", + "tags": [ + "Students (Mandatory)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The UID or login of the student" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ADMIN", + "YAKA", + "ING1_LYON", + "ING1_PARIS", + "ING1_RENNES", + "ING1_STRASBOURG", + "ING1_TOULOUSE" + ] + } + }, + "uid": { + "type": "number" + }, + "login": { + "type": "string" + }, + "avatarURL": { + "type": "string", + "nullable": true + }, + "quote": { + "type": "string", + "nullable": true + }, + "currentRoomSlug": { + "type": "string", + "nullable": true + }, + "banUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "guild": { + "type": "string", + "nullable": true + } + }, + "required": [ + "groups", + "uid", + "login", + "avatarURL", + "quote", + "currentRoomSlug", + "banUntil", + "guild" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + }, + "put": { + "operationId": "students-updateStudent", + "summary": "Update a student by UID or login", + "description": "Update a student by UID or login", + "tags": [ + "Students (Optional)" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "avatarURL": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "The URL of your avatar" + }, + "quote": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "Your quote" + }, + "guild": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "Your guild tag" + } + }, + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your student UID or login" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ADMIN", + "YAKA", + "ING1_LYON", + "ING1_PARIS", + "ING1_RENNES", + "ING1_STRASBOURG", + "ING1_TOULOUSE" + ] + } + }, + "uid": { + "type": "number" + }, + "login": { + "type": "string" + }, + "avatarURL": { + "type": "string", + "nullable": true + }, + "quote": { + "type": "string", + "nullable": true + }, + "currentRoomSlug": { + "type": "string", + "nullable": true + }, + "banUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "guild": { + "type": "string", + "nullable": true + } + }, + "required": [ + "groups", + "uid", + "login", + "avatarURL", + "quote", + "currentRoomSlug", + "banUntil", + "guild" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/students/{id}/ban": { + "post": { + "operationId": "students-banStudent", + "summary": "Ban a student by UID or login", + "description": "Ban a student by UID or by login. If no date is provided, the student will be unbanned.", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "banUntil": { + "type": "string", + "description": "The date until the student is banned" + }, + "reason": { + "type": "string", + "description": "The reason of the ban" + } + }, + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The UID or login of the student" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ADMIN", + "YAKA", + "ING1_LYON", + "ING1_PARIS", + "ING1_RENNES", + "ING1_STRASBOURG", + "ING1_TOULOUSE" + ] + } + }, + "uid": { + "type": "number" + }, + "login": { + "type": "string" + }, + "avatarURL": { + "type": "string", + "nullable": true + }, + "quote": { + "type": "string", + "nullable": true + }, + "currentRoomSlug": { + "type": "string", + "nullable": true + }, + "banUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "guild": { + "type": "string", + "nullable": true + } + }, + "required": [ + "groups", + "uid", + "login", + "avatarURL", + "quote", + "currentRoomSlug", + "banUntil", + "guild" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms": { + "get": { + "operationId": "rooms-getRooms", + "summary": "Get all rooms", + "description": "List all the rooms available to the student", + "tags": [ + "Rooms (Optional)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [], + "responses": { + "200": { + "description": "List of available rooms", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Room" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "rooms-createRoom", + "summary": "Create a room", + "description": "Create a new room", + "tags": [ + "Rooms (Optional)" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the room" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "The description of the room" + }, + "iconURL": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "The URL of the room's icon" + }, + "isPublic": { + "type": "boolean", + "description": "Whether the room is public or not. Defaults to false" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "The list of students allowed to join the room. Effective only if isPublic is false. Accepts both UIDs and logins. Defaults to an empty array" + }, + "studentsBlacklist": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "The list of students not allowed to join the room. Effective only if isPublic is true. Accepts both UIDs and logins. Defaults to an empty array" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "iconURL": { + "type": "string", + "nullable": true + }, + "canvasDimensions": { + "type": "number" + }, + "isPublic": { + "type": "boolean" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "type": "number" + } + }, + "studentsBlacklist": { + "type": "array", + "items": { + "type": "number" + } + }, + "ownerUid": { + "type": "number" + }, + "password": { + "type": "string", + "nullable": true + }, + "hidden": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "slug", + "name", + "description", + "iconURL", + "canvasDimensions", + "isPublic", + "studentsWhitelist", + "studentsBlacklist", + "ownerUid", + "password", + "hidden", + "deleted", + "createdAt" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}": { + "put": { + "operationId": "rooms-updateRoom", + "summary": "Update a room", + "description": "Update a room", + "tags": [ + "Rooms (Optional)" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the room" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "The description of the room" + }, + "iconURL": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ], + "description": "The URL of the room's icon" + }, + "isPublic": { + "type": "boolean", + "description": "Whether the room is public or not. Defaults to false" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "The list of students allowed to join the room. Effective only if isPublic is false. Accepts both UIDs and logins. Defaults to an empty array" + }, + "studentsBlacklist": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "The list of students not allowed to join the room. Effective only if isPublic is true. Accepts both UIDs and logins. Defaults to an empty array" + } + }, + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "iconURL": { + "type": "string", + "nullable": true + }, + "canvasDimensions": { + "type": "number" + }, + "isPublic": { + "type": "boolean" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "type": "number" + } + }, + "studentsBlacklist": { + "type": "array", + "items": { + "type": "number" + } + }, + "ownerUid": { + "type": "number" + }, + "password": { + "type": "string", + "nullable": true + }, + "hidden": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "slug", + "name", + "description", + "iconURL", + "canvasDimensions", + "isPublic", + "studentsWhitelist", + "studentsBlacklist", + "ownerUid", + "password", + "hidden", + "deleted", + "createdAt" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + }, + "delete": { + "operationId": "rooms-deleteRoom", + "summary": "Delete a room", + "description": "Delete a room", + "tags": [ + "Rooms (Optional)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/config": { + "get": { + "operationId": "rooms-getRoomConfig", + "summary": "Get the room config", + "description": "Get the room config", + "tags": [ + "Rooms (Mandatory)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ] + }, + "iconURL": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ] + }, + "canvasDimensions": { + "type": "number" + }, + "isPublic": { + "type": "boolean" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "type": "number" + } + }, + "studentsBlacklist": { + "type": "array", + "items": { + "type": "number" + } + }, + "ownerUid": { + "type": "number" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "palettes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "slug", + "name", + "description", + "iconURL", + "canvasDimensions", + "isPublic" + ], + "additionalProperties": false + }, + "settings": { + "type": "object", + "properties": { + "roomColors": { + "type": "string", + "pattern": "^(#[0-9a-fA-F]{6})(,#[0-9a-fA-F]{6})*$" + }, + "defaultCanvas": { + "type": "string" + }, + "hideCanvaAtStart": { + "type": "boolean" + } + }, + "required": [ + "roomColors" + ], + "additionalProperties": false + }, + "rateLimits": { + "type": "object", + "properties": { + "testsLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getCanvasLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getPixelLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "placePixelLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getRoomsLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getRoomConfigLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "createRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "updateRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "deleteRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getStudentLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "updateStudentLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "sendMessageLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "reportRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + } + }, + "required": [ + "testsLimiter", + "getCanvasLimiter", + "getPixelLimiter", + "placePixelLimiter", + "getRoomsLimiter", + "getRoomConfigLimiter", + "createRoomLimiter", + "updateRoomLimiter", + "deleteRoomLimiter", + "getStudentLimiter", + "updateStudentLimiter", + "sendMessageLimiter", + "reportRoomLimiter" + ], + "additionalProperties": false + } + }, + "required": [ + "metadata", + "settings" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "rooms-changeRoomConfig", + "summary": "Load another json room config", + "description": "Load another json room config", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "settings": { + "type": "object", + "properties": { + "roomColors": { + "type": "string", + "description": "The colors of the room" + } + }, + "additionalProperties": false, + "description": "The settings to change" + }, + "events": { + "type": "object", + "properties": { + "colorationEvent": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The type of the coloration event" + }, + "groups": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The groups of the coloration event" + }, + "colorsSubSet": { + "type": "array", + "items": { + "type": "number" + }, + "description": "The colors subset of the coloration event" + } + }, + "required": [ + "type" + ], + "additionalProperties": false + }, + "radiusEvent": { + "type": "object", + "properties": { + "radius": { + "type": "number", + "description": "The radius of the event" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "rateLimitsOverride": { + "type": "object", + "properties": { + "testsLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getCanvasLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getPixelLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "placePixelLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getRoomsLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getRoomConfigLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "createRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "updateRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "deleteRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "getStudentLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "updateStudentLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "sendMessageLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + }, + "reportRoomLimiter": { + "type": "object", + "properties": { + "limit": { + "type": "number" + }, + "interval": { + "type": "number" + } + }, + "required": [ + "limit", + "interval" + ], + "additionalProperties": false + } + }, + "required": [ + "testsLimiter", + "getCanvasLimiter", + "getPixelLimiter", + "placePixelLimiter", + "getRoomsLimiter", + "getRoomConfigLimiter", + "createRoomLimiter", + "updateRoomLimiter", + "deleteRoomLimiter", + "getStudentLimiter", + "updateStudentLimiter", + "sendMessageLimiter", + "reportRoomLimiter" + ], + "additionalProperties": false, + "description": "The rate limits to override" + } + }, + "required": [ + "events" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/report": { + "post": { + "operationId": "rooms-reportRoom", + "summary": "Report a room", + "description": "Report a room", + "tags": [ + "Misc" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "The reason for the report" + } + }, + "required": [ + "reason" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/yeet": { + "post": { + "operationId": "rooms-yeetFromRoom", + "summary": "Yeet a student from a room", + "description": "Yeet a student from a room", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slug": { + "type": "string", + "description": "The slug of the room. If empty, all rooms will be yeeted" + }, + "studentsList": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "description": "The list of students to yeet. If empty, all students will be yeeted" + } + }, + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/getGuildProportion": { + "get": { + "operationId": "rooms-canvas-getGuildProportion", + "summary": "Get informations about which guild placed each pixels", + "description": "Get informations about which guild placed each pixels", + "tags": [ + "Rooms (Optional)" + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/score": { + "get": { + "operationId": "rooms-canvas-getScores", + "summary": "Get the guild score of a room", + "description": "Get the guild score of a room", + "tags": [ + "Rooms (Optional)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "guilds": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "points": { + "type": "number" + } + }, + "required": [ + "name", + "points" + ], + "additionalProperties": false + } + } + }, + "required": [ + "guilds" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/canvas": { + "get": { + "operationId": "rooms-canvas-getCanvas", + "summary": "Get the canvas of a room", + "description": "Get the canvas of a room", + "tags": [ + "Rooms (Mandatory)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pixels": { + "type": "string", + "description": "The pixels of the room" + } + }, + "required": [ + "pixels" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/canvas/pixels": { + "get": { + "operationId": "rooms-canvas-getPixel", + "summary": "Get the pixels of a room", + "description": "Get the pixels of a room", + "tags": [ + "Rooms (Mandatory)" + ], + "security": [ + { + "Authorization": [] + } + ], + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + }, + { + "name": "posX", + "in": "query", + "required": true, + "schema": { + "type": "number" + }, + "description": "The X position of the pixel" + }, + { + "name": "posY", + "in": "query", + "required": true, + "schema": { + "type": "number" + }, + "description": "The Y position of the pixel" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "roomSlug": { + "type": "string" + }, + "posX": { + "type": "number" + }, + "posY": { + "type": "number" + }, + "color": { + "type": "number" + }, + "timestamp": { + "anyOf": [ + { + "type": "number" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ] + }, + "placedByUid": { + "anyOf": [ + { + "type": "number" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ] + } + }, + "required": [ + "roomSlug", + "posX", + "posY", + "color", + "timestamp", + "placedByUid" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + }, + "post": { + "operationId": "rooms-canvas-placePixel", + "summary": "Place a pixel in a room", + "description": "Place a pixel in a room", + "tags": [ + "Rooms (Mandatory)" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "posX": { + "type": "number", + "description": "The X position of the pixel" + }, + "posY": { + "type": "number", + "description": "The Y position of the pixel" + }, + "color": { + "type": "number", + "description": "The color index of the pixel" + } + }, + "required": [ + "posX", + "posY", + "color" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "roomSlug": { + "type": "string" + }, + "posX": { + "type": "number" + }, + "posY": { + "type": "number" + }, + "color": { + "type": "number" + }, + "timestamp": { + "anyOf": [ + { + "type": "number" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ] + }, + "placedByUid": { + "anyOf": [ + { + "type": "number" + }, + { + "enum": [ + "null" + ], + "nullable": true + } + ] + } + }, + "required": [ + "roomSlug", + "posX", + "posY", + "color", + "timestamp", + "placedByUid" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/canvas/resize": { + "put": { + "operationId": "rooms-canvas-resizeCanvas", + "summary": "Resize the canvas of a room", + "description": "Resize the canvas of a room", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "newCanvasDimensions": { + "type": "number", + "description": "The new size of the room" + } + }, + "required": [ + "newCanvasDimensions" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "iconURL": { + "type": "string", + "nullable": true + }, + "canvasDimensions": { + "type": "number" + }, + "isPublic": { + "type": "boolean" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "type": "number" + } + }, + "studentsBlacklist": { + "type": "array", + "items": { + "type": "number" + } + }, + "ownerUid": { + "type": "number" + }, + "password": { + "type": "string", + "nullable": true + }, + "hidden": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "slug", + "name", + "description", + "iconURL", + "canvasDimensions", + "isPublic", + "studentsWhitelist", + "studentsBlacklist", + "ownerUid", + "password", + "hidden", + "deleted", + "createdAt" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/canvas/reset": { + "put": { + "operationId": "rooms-canvas-resetCanvas", + "summary": "Reset the canvas of a room", + "description": "Reset the canvas of a room", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "resetOnDefault": { + "type": "boolean", + "description": "Reset the canvas on the default image if exists" + } + }, + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "iconURL": { + "type": "string", + "nullable": true + }, + "canvasDimensions": { + "type": "number" + }, + "isPublic": { + "type": "boolean" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "type": "number" + } + }, + "studentsBlacklist": { + "type": "array", + "items": { + "type": "number" + } + }, + "ownerUid": { + "type": "number" + }, + "password": { + "type": "string", + "nullable": true + }, + "hidden": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "slug", + "name", + "description", + "iconURL", + "canvasDimensions", + "isPublic", + "studentsWhitelist", + "studentsBlacklist", + "ownerUid", + "password", + "hidden", + "deleted", + "createdAt" + ], + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + }, + "/rooms/{slug}/canvas/load": { + "post": { + "operationId": "rooms-canvas-loadCanvas", + "summary": "Load the canvas of a room", + "description": "Load the canvas of a room", + "tags": [ + "Admin" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "backupId": { + "type": "number", + "description": "The id of the backup" + } + }, + "required": [ + "backupId" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The slug of the room" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InvalidToken": { + "value": { + "code": "UNAUTHORIZED", + "message": "Invalid token" + } + }, + "TokenExpired": { + "value": { + "code": "UNAUTHORIZED", + "message": "Token expired" + } + }, + "Unauthenticated": { + "value": { + "code": "UNAUTHORIZED", + "message": "Unauthenticated" + } + }, + "Banned": { + "value": { + "code": "UNAUTHORIZED", + "message": "You are banned" + } + } + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "NotAdmin": { + "value": { + "code": "FORBIDDEN", + "message": "You are not an admin" + } + } + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "TooManyRequests": { + "value": { + "code": "TOO_MANY_REQUESTS", + "message": "Too many requests" + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + }, + "examples": { + "InternalServerError": { + "value": { + "code": "INTERNAL_SERVER_ERROR", + "message": "An error occured" + } + } + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "Authorization": { + "type": "http", + "scheme": "bearer" + } + }, + "responses": { + "error": { + "description": "Error response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + }, + "issues": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + } + }, + "required": [ + "message", + "code" + ], + "additionalProperties": false + } + } + } + } + }, + "schemas": { + "Room": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "iconURL": { + "type": "string", + "nullable": true + }, + "canvasDimensions": { + "type": "number" + }, + "isPublic": { + "type": "boolean" + }, + "studentsWhitelist": { + "type": "array", + "items": { + "type": "number" + } + }, + "studentsBlacklist": { + "type": "array", + "items": { + "type": "number" + } + }, + "ownerUid": { + "type": "number" + }, + "password": { + "type": "string", + "nullable": true + }, + "hidden": { + "type": "boolean" + }, + "deleted": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + } + }, + "Student": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ADMIN", + "YAKA", + "ING1_LYON", + "ING1_PARIS", + "ING1_RENNES", + "ING1_STRASBOURG", + "ING1_TOULOUSE" + ] + } + }, + "uid": { + "type": "number" + }, + "login": { + "type": "string" + }, + "avatarURL": { + "type": "string", + "nullable": true + }, + "quote": { + "type": "string", + "nullable": true + }, + "currentRoomSlug": { + "type": "string", + "nullable": true + }, + "banUntil": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "guild": { + "type": "string", + "nullable": true + } + } + }, + "Error": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + }, + "tags": [ + { + "name": "Misc" + }, + { + "name": "Tests" + }, + { + "name": "Rooms (Mandatory)" + }, + { + "name": "Rooms (Optional)" + }, + { + "name": "Students (Mandatory)" + }, + { + "name": "Students (Optional)" + }, + { + "name": "Admin" + } + ] +} \ No newline at end of file diff --git a/src/components/notifications/index.html b/src/components/notifications/index.html new file mode 100644 index 0000000..45cf284 --- /dev/null +++ b/src/components/notifications/index.html @@ -0,0 +1,12 @@ +
+
+ +
+
+ {{title}} + {{content}} +
+ + + +
diff --git a/src/components/rooms/index.html b/src/components/rooms/index.html new file mode 100644 index 0000000..81c69fd --- /dev/null +++ b/src/components/rooms/index.html @@ -0,0 +1,10 @@ + diff --git a/src/components/rooms/message.html b/src/components/rooms/message.html new file mode 100644 index 0000000..82c4d94 --- /dev/null +++ b/src/components/rooms/message.html @@ -0,0 +1,8 @@ +
+
+ + + {{sent_at}} +
+ {{message_content}} +
diff --git a/src/components/rooms/upsert.html b/src/components/rooms/upsert.html new file mode 100644 index 0000000..c193daf --- /dev/null +++ b/src/components/rooms/upsert.html @@ -0,0 +1,70 @@ +
+
+
+

{{form_title}}

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/src/components/rooms/user-event.html b/src/components/rooms/user-event.html new file mode 100644 index 0000000..85abc69 --- /dev/null +++ b/src/components/rooms/user-event.html @@ -0,0 +1,3 @@ +
+ {{message_content}} +
diff --git a/src/components/students/update.html b/src/components/students/update.html new file mode 100644 index 0000000..07a94e3 --- /dev/null +++ b/src/components/students/update.html @@ -0,0 +1,42 @@ +
+
+
+

Update Profile

+
+
+ + +
+ +
+ + +
+
+ + +
+
+ + +
+
+
diff --git a/src/pages/complete/epita/index.html b/src/pages/complete/epita/index.html new file mode 100644 index 0000000..f066fbe --- /dev/null +++ b/src/pages/complete/epita/index.html @@ -0,0 +1,12 @@ + + + + + + E/PLACE + + + + + + diff --git a/src/pages/complete/epita/index.js b/src/pages/complete/epita/index.js new file mode 100644 index 0000000..5b66ba4 --- /dev/null +++ b/src/pages/complete/epita/index.js @@ -0,0 +1,2 @@ +// FIXME: This file should handle the auth redirection +// Get the code from the URL parameters and redirect to the relevant page diff --git a/src/pages/debug/debug.html b/src/pages/debug/debug.html new file mode 100644 index 0000000..9a26d18 --- /dev/null +++ b/src/pages/debug/debug.html @@ -0,0 +1,58 @@ + + + + + + + Debug Page + + + +
+ +
+

Debug Dashboard

+ +
+

Local Storage Tokens

+
+

Access Token

+

{{ token }}

+

Refresh Token

+

{{ refresh_token }}

+
+
+ + + +
+
+ +
+

User Profile

+
+ User Avatar +
+ + +
+
+
+ + + +
+
+ +
+

Error Simulation

+
+ + + +
+
+
+ + + diff --git a/src/pages/debug/debug.js b/src/pages/debug/debug.js new file mode 100644 index 0000000..c6b25bd --- /dev/null +++ b/src/pages/debug/debug.js @@ -0,0 +1,122 @@ +import $ from "jquery"; +import debugHtml from "./debug.html"; +import { displayStudentProfile, expired } from "./utils"; +import { createAlert } from "../../utils/notify"; + +const { authedAPIRequest, authenticate } = await import("../../utils/auth.js"); +const authed = authedAPIRequest ?? (() => {}); + +function clearLocalStorage() { + localStorage.removeItem("token"); + localStorage.removeItem("refresh_token"); +} + +function clearUserProfile(log = true) { + $("#profile-info-login").text("No login"); + $("#profile-info-quote").text("No quote"); + log && createAlert("Debug", "Clear user profile info", "success"); +} + +function refreshLocalStorage() { + $("#token").text(localStorage.getItem("token") ?? "N/A"); + $("#refresh_token").text(localStorage.getItem("refresh_token") ?? "N/A"); +} + +async function refreshProfile() { + await displayStudentProfile() + .then(() => { + createAlert("Debug", "Display profile succeed", "success"); + }) + .catch((_error) => { + createAlert("Debug", "Cannot display profile", "error"); + clearUserProfile(false); + }); +} + +async function refresh() { + refreshLocalStorage(); + await refreshProfile(); +} + +(() => { + if (import.meta.env.MODE !== "debug") { + return; + } + + $.get(debugHtml, function (response) { + $("body").html(response); + refreshLocalStorage(); + }).fail(function (_xhr, _status, error) { + console.error("Error fetching debug HTML:", error); + }); + + $(document).on("click", "#launchOIDC", async function () { + clearLocalStorage(); + if (await authenticate()) { + createAlert("Debug", "OIDC succeed", "error"); + } else { + createAlert("Debug", "OIDC failed", "error"); + } + + await refresh(); + }); + $(document).on("click", "#displayProfile", refreshProfile); + $(document).on("click", "#hideProfile", clearUserProfile); + $(document).on("click", "#errorBtn", async function () { + await authed("/tests/error", { method: "GET" }); + createAlert("Debug", "Error response generated", "success"); + await refresh(); + }); + + $(document).on("click", "#invalidToken", async function () { + await authedAPIRequest("/tests/invalid-token", { method: "POST" }); + createAlert("Debug", "Invalid token response generated", "success"); + await refresh(); + }); + + $(document).on("click", "#expiredTokenBtn", async function () { + expired(); + let res = await authedAPIRequest("/tests/valid", { method: "GET" }); + + res = await res.json(); + if (res.response === "A valid response") { + createAlert( + "Debug", + "Token has been refreshed and the request has been re-send", + "success", + ); + } else { + createAlert("Debug", "An error occured", "error"); + } + + await refresh(); + }); + + $(document).on("click", "#deleteTokenBtn", async function () { + localStorage.removeItem("token"); + createAlert("Debug", "Token removed from local storage", "success"); + await refresh(); + }); + + $(document).on("click", "#deleteRefreshTokenBtn", async function () { + localStorage.removeItem("refresh_token"); + createAlert( + "Debug", + "Refresh token removed from local storage", + "success", + ); + await refresh(); + }); + + $(document).on("click", "#clearTokens", async function () { + createAlert( + "Debug", + "Token and refresh token removed from local storage", + "success", + ); + clearLocalStorage(); + await refresh(); + }); + + refresh(); +})(); diff --git a/src/pages/debug/index.css b/src/pages/debug/index.css new file mode 100644 index 0000000..0d50b3e --- /dev/null +++ b/src/pages/debug/index.css @@ -0,0 +1,128 @@ +:root { + --bg-page: #f0f2f5; + --bg-card: #5A6991; + --bg-token: #e8eaed; + --bg-profile: #e8eaed; + --border-card: #d1d5db; + --text-heading: #111827; + --text-label: #6b7280; + --text-body: #1f2937; + --text-quote: #6b7280; + --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.08); + --btn-bg: #455069; + --btn-bg-hover: #111827; + --btn-text: #ffffff; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg-page: #0f1117; + --bg-card: #1a1d27; + --bg-token: #2a2d3a; + --bg-profile: #2a2d3a; + --border-card: #2e3348; + --text-heading: #f0f2f5; + --text-label: #8b92a5; + --text-body: #e2e5ed; + --text-quote: #8b92a5; + --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.4); + --btn-bg: #3B4257; + --btn-bg-hover: #51596E; + --btn-text: #e2e5ed; + + } +} + +body { + background-color: var(--bg-page); + color: var(--text-body); + transition: background-color 0.2s, color 0.2s; +} + +.container { + width: 800px; +} + +.card { + background-color: var(--bg-card); + border: 1px solid var(--border-card); + border-radius: 10px; + padding: 20px; + margin: 20px; + box-shadow: var(--shadow-card); +} + +.card h2 { + margin-top: 0; + margin-bottom: 15px; + font-size: 1.5em; + color: var(--text-heading); +} + +.token-section h3 { + margin-bottom: 5px; + font-size: 1.1em; + color: var(--text-body); +} + +.token-text { + background-color: var(--bg-token); + padding: 10px; + border-radius: 5px; + word-break: break-all; + color: var(--text-body); +} + +.StudentProfile { + display: flex; + align-items: center; + gap: 15px; + width: 30%; + margin: 10px 0 30px; + border-radius: 5px; + padding: 7px 10px; + background-color: var(--bg-profile); +} + +.Avatar { + width: 60px; + height: 60px; + border-radius: 50%; + object-fit: cover; + border: 1px solid var(--border-card); +} + +.TextContainer { + display: flex; + flex-direction: column; +} + +.Login { + font-weight: bold; + font-size: 1.1em; + color: var(--text-body); +} + +.Quote { + font-style: italic; + color: var(--text-quote); +} + +.button-group { + display: flex; + justify-content: space-around; +} + +button { + background-color: var(--btn-bg); + color: var(--btn-text); + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 0.9em; + cursor: pointer; +} + +button:hover { + background-color: var(--btn-bg-hover); +} diff --git a/src/pages/debug/utils.js b/src/pages/debug/utils.js new file mode 100644 index 0000000..0701434 --- /dev/null +++ b/src/pages/debug/utils.js @@ -0,0 +1,57 @@ +// FIXME: File that provide utils function for the debug page +import $ from "jquery"; +import jwt_decode from "jwt-decode"; +import { createAlert } from "../../utils/notify"; + +export async function displayStudentProfile() { + const token = localStorage.getItem("token"); + const decoded = jwt_decode(token); + + const _uid = decoded.uid; + + // You have to write a request to fetch your informations + const request_result = null; + + if (request_result === null) { + createAlert( + "debug", + "Fetch not implemented in the pages/debug/utils.js file", + "error", + ); + } + + const student_resources = await request_result?.json(); + + $("#profile-info-avatar").attr( + "src", + student_resources.avatarURL ?? "/default-avatar.png", + ); + + $("#profile-info-login").text(student_resources.login); + $("#profile-info-quote").text(student_resources.quote); +} + +export function expired() { + const token = localStorage.getItem("token"); + const splited_token = token.split("."); + + let base64 = splited_token[1].replace(/-/g, "+").replace(/_/g, "/"); + + while (base64.length % 4 !== 0) { + base64 += "="; + } + + const parts = JSON.parse(atob(base64)); + + parts["exp"] = 0; + + const recodedValue = btoa(JSON.stringify(parts)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=/g, ""); + + splited_token[1] = recodedValue; + const expiredToken = splited_token.join("."); + + localStorage.setItem("token", expiredToken); +} diff --git a/src/pages/index.css b/src/pages/index.css new file mode 100644 index 0000000..4ebe9b7 --- /dev/null +++ b/src/pages/index.css @@ -0,0 +1,78 @@ +@import url(http://fonts.googleapis.com/css?family=Barlow:800); +@import url(http://fonts.googleapis.com/css?family=Montserrat:400,700); + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif, Barlow; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + justify-content: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; + margin: 0; +} + +h2 { + margin: 0; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} +input { + border: none; +} +.token-text { + max-width: 10ch; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/src/pages/index.html b/src/pages/index.html new file mode 100644 index 0000000..e0979b5 --- /dev/null +++ b/src/pages/index.html @@ -0,0 +1,239 @@ + + + + + + E/PLACE + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+
+

+ {{room_name}} +

+ {{room_description}} +
+
+
+ + +
+
+
+ +
+
+
+
+ + GUILDS +
+
    +
    +
    + + +
    +
    +
    + +
    + + {{student_guild}} + {{student_quote}} +
    +
    +
    + {{date_placed}} + {{time_placed}} +
    +
    +
    + + +
    +
    +
    +
    + X=0 + Y=0 +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + diff --git a/src/pages/index.js b/src/pages/index.js new file mode 100644 index 0000000..5a404b3 --- /dev/null +++ b/src/pages/index.js @@ -0,0 +1,6 @@ +// FIXME: This is the entry point of the application, write your code here + +import { calculateLayout } from "./utils"; + +// Initialize the layout +calculateLayout(); diff --git a/src/pages/styles.less b/src/pages/styles.less new file mode 100644 index 0000000..ceec6ef --- /dev/null +++ b/src/pages/styles.less @@ -0,0 +1,1080 @@ +@base: rgba(5, 6, 26, 0.72); +@dark: #05061a; +@light: #f0f0f0; +@accent: #ff4603; + +.App { + background-color: @dark; + min-height: 100vh; + margin: 0 auto; + overflow: hidden; + padding: 0rem; + position: relative; + text-align: center; + width: 100vw; +} + +.AlertContainer { + position: absolute; + top: 0; + right: 0; + display: flex; + flex-direction: column-reverse; + align-items: flex-end; + justify-content: flex-end; + gap: 1rem; + padding: 2rem; + z-index: 3; + pointer-events: none; + + .Alert { + position: relative; + background-color: #fff; + display: flex; + align-items: center; + gap: 1rem; + padding: 0.5rem; + width: 100%; + pointer-events: all; + + box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; + + .Icon { + font-size: 28px; + } + + .AlertBody { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + margin: 0.5rem 2rem 0.5rem 0; + text-align: left; + font-family: Barlow; + color: @dark; + + .AlertTitle { + font-size: 1rem; + font-weight: 800; + text-transform: uppercase; + } + .AlertContent { + font-size: 0.75rem; + opacity: 50%; + } + } + + .AlertClose { + position: absolute; + top: 0.5rem; + right: 0.5rem; + font-size: 1em; + color: @dark; + } + } + + .AlertSuccess { + border-left: 5px solid #49d761; + + .Icon { + color: #49d761; + } + } + .AlertWarning { + border-left: 5px solid #ff9f1c; + + .Icon { + color: #ff9f1c; + } + } + .AlertError { + border-left: 5px solid #fd2020; + + .Icon { + color: #fd2020; + } + } +} + +.Container { + column-gap: 2em; + display: grid; + flex-direction: row; + grid-auto-flow: row dense; + // Fully openned sidebars layout + // -> grid-template-columns: 1fr 2.5fr 1fr; + // Closed sidebars layout + grid-template-columns: 0fr 2.5fr 0fr; + height: calc(100vh - 4rem); + overflow: hidden; + padding: 2rem; + + transition: all 0.5s ease-in-out; +} + +.RoomList { + background-color: @base; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(@light, 0.08); + border-radius: 0.5rem; + display: grid; + flex-grow: 0; + gap: 0.5em 0em; + grid-template-rows: 1fr 8.25fr 0.75fr; + overflow: hidden; + padding: 1.5rem; + z-index: 1; + // Fully openned sidebar + // -> opacity: 1; + // Closed sidebar + opacity: 0; + + transition: opacity 0.5s ease-in-out; + + .Header { + display: flex; + align-items: center; + justify-content: space-between; + overflow: hidden; + padding: 0.5rem 1rem; + + .Title { + color: @light; + font-family: Barlow; + font-size: 3rem; + font-weight: 800; + grid-row-start: 1; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; + } + } + + .ListContainer { + display: flex; + flex-direction: column; + gap: 1em; + margin: 1rem 0rem; + overflow: auto; + padding: 0rem; + } + + .ListSearchBar { + display: flex; + flex-direction: column; + gap: 0.5em 0em; + + .FilterContainer { + background-color: rgba(black, 0.5); + align-items: center; + display: flex; + font-family: Barlow; + font-weight: 800; + font-size: 0.75rem; + gap: 0em 1em; + overflow: hidden; + text-align: start; + white-space: nowrap; + padding: 0.5rem; + border-radius: 0.5rem; + + .FilterText { + margin-left: 0.5rem; + opacity: 0.5; + font-size: 0.8rem; + } + + .FilterHeader { + display: flex; + font-family: Barlow; + font-weight: 800; + overflow: scroll; + scrollbar-width: none; + text-align: start; + gap: 0.5em; + + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } + + .Filter { + background-color: rgba(@light, 0.25); + border-radius: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5em; + padding: 0.25rem 0.5rem; + font-size: 0.65rem; + } + } + } + } + + .RoomsContainer { + display: flex; + flex-direction: column; + gap: 0.5rem; + grid-row-start: 2; + overflow: scroll; + + .Room { + background-color: rgba(black, 0.25); + border-radius: 0.5rem; + display: flex; + gap: 1em; + align-items: center; + padding: 0.5rem; + + &:disabled { + border: @accent 0.15rem solid; + cursor: not-allowed; + } + + .Avatar { + height: 4rem; + width: 4rem; + object-fit: cover; + aspect-ratio: 1/1; + } + + .TextContainer { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.5em; + color: @light; + overflow: hidden; + + .Name { + font-family: Barlow; + font-size: 1rem; + font-weight: 800; + text-align: start; + text-transform: uppercase; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + .RoomPrivacy { + margin-right: 0.25rem; + } + } + + .RoomOwner { + font-family: Montserrat; + font-size: 0.65rem; + font-weight: 700; + text-align: start; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + + .ListRoomDivider { + background-color: rgba(@light, 0.25); + height: 0.1rem; + margin: 0.5rem 0rem; + } + } + + .StudentProfile { + align-items: center; + background-color: @light; + border-radius: 5rem; + column-gap: 0.5em; + display: flex; + flex-direction: row; + grid-row-start: 3; + overflow: hidden; + padding: 0.5rem; + + .Avatar { + border-radius: 100%; + max-height: 3rem; + max-width: 3rem; + object-fit: cover; + aspect-ratio: 1/1; + } + + .TextContainer { + display: flex; + flex-direction: column; + justify-content: center; + margin-right: 0.5rem; + overflow: hidden; + color: black; + text-align: start; + overflow: hidden; + + .Login { + font-family: Barlow; + font-size: 1rem; + font-weight: 800; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + + .Quote { + font-family: Montserrat; + font-size: 0.7rem; + font-weight: 700; + opacity: 65%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .ModifyProfileButton { + background-color: transparent; + color: @dark; + padding: 0.25rem; + margin-left: auto; + margin-right: 0.5rem; + border-radius: 100%; + } + } +} + +.RoomCanvas { + display: grid; + flex-direction: column; + flex-grow: 0; + grid-template-rows: 0.25fr 9.75fr; + min-width: 50vw; + row-gap: 0em; + overflow: hidden; + + .Header { + background-color: @base; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(@light, 0.08); + border-radius: 0.5rem; + align-items: center; + justify-content: space-between; + display: flex; + flex-direction: row; + grid-row-start: 1; + padding: 0.5rem; + z-index: 1; + + .Header-left { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + justify-content: flex-start; + padding-left: 6rem; + + .HeaderButton { + background-color: @accent; + border-radius: 0.5rem; + border: 0px solid transparent; + color: @light; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + padding: 0.35rem 0.75rem; + text-transform: uppercase; + } + } + + .Header-right { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + justify-content: flex-start; + padding-right: 6rem; + + .HeaderButton { + background-color: @accent; + border-radius: 0.5rem; + border: 0px solid transparent; + color: @light; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + padding: 0.35rem 0.75rem; + text-transform: uppercase; + } + } + + .Header-center { + display: flex; + align-items: center; + gap: 0.75rem; + flex: 1; + justify-content: center; + + .TextContainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .Title { + color: @light; + font-family: Barlow; + font-size: 2rem; + font-weight: 800; + overflow: hidden; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; + } + + .Description { + // Start with the description hidden. + // It needs to be visible if the room has a description. + + display: none; + position: relative; + top: -0.25rem; + color: @light; + font-family: Montserrat; + font-size: 0.5rem; + font-weight: 700; + opacity: 0.5; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + .HeaderDivider { + background-color: rgba(@light, 0.25); + height: 1.5rem; + margin: 0; + width: 0.1rem; + flex-shrink: 0; + } + } + + .Header-right { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.5rem; + flex: 1; + + .ButtonContainer { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + + .ReportButton { + background-color: @accent; + border-radius: 0.5rem; + border: 0px solid transparent; + color: @light; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + padding: 0.35rem 0.75rem; + text-transform: uppercase; + } + } + } +} + + .GuildLeaderboard { + margin-top: 0.75rem; + top: calc(0.25fr + 1rem); + right: 1rem; + z-index: 2; + width: 185px; + padding: 0.75rem; + + background-color: @base; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border-radius: 0.5rem; + border: 1px solid rgba(@light, 0.08); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); + + pointer-events: none; + user-select: none; + + .LeaderboardHeader { + display: flex; + align-items: center; + gap: 0.5rem; + padding-bottom: 0.5rem; + margin-bottom: 0.5rem; + border-bottom: 1px solid rgba(@light, 0.1); + + i { + font-size: 0.65rem; + color: @accent; + } + + .LeaderboardTitle { + font-family: Barlow; + font-size: 0.65rem; + font-weight: 800; + letter-spacing: 0.15em; + text-transform: uppercase; + color: @accent; + } + } + + .LeaderboardList { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.35rem; + } + + .LeaderboardItem { + display: grid; + grid-template-columns: 14px 1fr auto; + align-items: center; + gap: 0.5rem; + + .Rank { + font-family: Barlow; + font-size: 0.65rem; + font-weight: 800; + text-align: center; + color: rgba(@light, 0.35); + } + + .GuildName { + font-family: Barlow; + font-size: 0.7rem; + font-weight: 700; + color: @light; + text-transform: uppercase; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .GuildPoints { + font-family: Montserrat; + font-size: 0.65rem; + font-weight: 700; + color: rgba(@light, 0.45); + white-space: nowrap; + } + + &.rank-1 { + .Rank { color: @accent; } + .GuildPoints { color: @accent; opacity: 0.85; } + } + + &.rank-2 { + .Rank { color: rgba(@light, 0.8); } + } + + &.rank-3 { + .Rank { color: rgba(@light, 0.55); } + } + } + } + + .CanvasContainer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + grid-row-start: 2; + overflow: hidden; + z-index: 0; + + .Canvas { + transform: translate(0, 0) scale(2.5); + } + + .Selector { + position: absolute; + inset: 0; + width: 1px; + height: 1px; + margin: auto; + pointer-events: none; + } + + .Tooltip { + // The tooltip is a flexbox + // -> display: flex; + // Start with the tooltip hidden + display: none; + + position: absolute; + top: 50%; + left: 50%; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + transform: translate(-50%, -150%); + background-color: @base; + border-radius: 0.5rem; + border: @accent 0.15rem solid; + padding: 0.5rem; + + .Header { + display: flex; + gap: 0.75rem; + + .PlacedByInfo { + position: relative; + display: inline-block; + height: 3rem; + width: 3rem; + + .Avatar { + border-radius: 100%; + width: 100%; + height: 100%; + object-fit: cover; + aspect-ratio: 1/1; + } + + .Profile { + visibility: hidden; + background-color: @dark; + color: @light; + text-align: center; + padding: 0.5rem; + border-radius: 0.5rem; + + bottom: 100%; + left: 50%; + transform: translate(-50%, 0); + position: absolute; + z-index: 1; + + &::after { + content: " "; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: @dark transparent transparent transparent; + } + + .Login { + font-family: Barlow; + font-size: 0.8rem; + font-weight: 800; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + + .Guild { + font-family: Barlow; + font-size: 0.8rem; + font-weight: 800; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .Quote { + font-family: Montserrat; + font-size: 0.7rem; + font-weight: 700; + opacity: 0.5; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + + &:hover .Profile { + visibility: visible; + } + } + + .TextContainer { + display: flex; + flex-direction: column; + justify-content: center; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + text-align: left; + } + } + + .ButtonContainer { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.5rem; + width: 100%; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + + .ColorPicker { + padding: 0.2rem; + outline: none; + color: white; + border: white 0.1rem solid; + } + + .PlaceButton { + padding: 0.2rem; + border: @accent 0.1rem solid; + border-radius: 0.5rem; + outline: none; + font-family: Barlow; + font-size: 0.75rem; + font-weight: 800; + + &:active { + background-color: @accent; + color: @light; + } + + &:disabled { + background-color: @dark; + color: @accent; + } + } + } + } + } + + .PositionTooltip { + background-color: rgba(@dark, 0.25); + padding: 0.5rem; + margin: 0.5rem auto auto 0; + border-radius: 0.5rem; + font-family: Montserrat; + font-weight: 700; + z-index: 2; + } + + .ColorWheelContainer { + // The color wheel is a flexbox + // -> display: block; + // Start with the color wheel hidden + display: none; + + background-color: @base; + margin: auto 0 0 auto; + padding: 0.5rem; + max-height: calc(100% - 12rem); + overflow: scroll; + border-radius: 1rem; + border: @accent 0.15rem solid; + z-index: 1; + + min-block-size: -webkit-fill-available; + + -ms-overflow-style: none; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } + + transition: all 0.5s ease-in-out; + + .ColorWheel { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + width: 100%; + margin: 0.25rem 0; + } + } +} + +.RoomChat { + display: grid; + flex-grow: 0; + grid-auto-flow: row; + grid-template-rows: 1fr 8fr; + overflow: hidden; + padding: 0rem; + z-index: 1; + // Fully openned sidebar + // -> opacity: 1; + // Closed sidebar + opacity: 0; + + transition: opacity 0.5s ease-in-out; + + .ChatContainer { + background-color: @base; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(@light, 0.08); + border-radius: 0.5rem; + display: grid; + gap: 1em; + grid-row-start: 2; + grid-template-rows: 0.5fr 6.75fr 0.75fr; + grid-template-columns: 1fr; + overflow: hidden; + padding: 0.8rem; + + .Header { + display: flex; + flex-direction: row-reverse; + align-items: center; + } + + .ChatMessageContainer { + display: flex; + flex-direction: column-reverse; + overflow: auto; + padding: 0rem; + row-gap: 1em; + + .ChatMessage { + color: @light; + background-color: rgba(black, 0.25); + border-radius: 0.5rem; + display: flex; + flex-direction: column; + padding: 0.5rem; + + .MessageHeader { + column-gap: 0.5em; + display: flex; + padding: 0.5rem; + overflow: hidden; + + .Avatar { + border-radius: 100%; + max-height: 2rem; + max-width: 2rem; + object-fit: cover; + aspect-ratio: 1/1; + } + + .Login { + font-family: Barlow; + font-weight: 800; + text-align: left; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .Time { + font-family: Barlow; + margin-left: auto; + text-align: end; + } + } + } + + .ChatUserEvent { + background-color: rgba(black, 0.25); + border-radius: 0.5rem; + display: flex; + flex-direction: column; + padding: 0.25rem; + opacity: 0.5; + } + + .ChatMessageMentionned { + background-color: rgba(@accent, 0.25); + } + + .MessageContent { + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 400; + overflow-wrap: break-word; + padding: 0.5rem; + text-align: start; + } + } + } +} + +.Hidden { + width: 0; + height: 0; + margin: 0; + padding: 0; +} + +.CloseButton { + background-color: @accent; + border-radius: 0.5rem; + border: 0px solid transparent; + color: @light; + display: block; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + max-height: 2rem; + padding: 0.5rem 0.75rem; + text-transform: uppercase; +} + +.InputContainer { + background-color: rgba(black, 0.5); + border-radius: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem; + overflow: hidden; + + .Divider { + background-color: @light; + height: 2rem; + width: 0.1rem; + opacity: 0.75; + } +} + +.RoomButton { + background-color: rgba(@light, 0.25); + padding: 0.35rem 0.5rem; + font-size: 0.75rem; +} + +.ChatTimeout { + --fill-percent: 0; + + border-radius: 50%; + padding: 0.25rem 0.5rem; + + background: radial-gradient( + closest-side, + @dark 80%, + transparent 0 99.9%, + @dark 0 + ), + conic-gradient(@accent calc(var(--fill-percent) * 1%), @dark 0); +} + +.Input { + background-color: rgba(black, 0); + color: @light; + flex-grow: 1; + font-family: Montserrat; + font-size: 0.75rem; + font-weight: 700; + opacity: 1; + padding: 0.5rem; + overflow: hidden; + text-overflow: ellipsis; +} + +.FormOverlay { + position: absolute; + inset: 0; + background-color: rgba(@dark, 0.95); + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + + .StylisedForm { + background-color: @base; + padding: 2rem 1rem; + border: 0.25rem solid @accent; + border-radius: 0.5rem; + display: flex; + flex-direction: column; + gap: 1rem; + align-items: center; + justify-content: center; + font-family: Barlow; + min-width: 65vw; + max-width: 90vw; + color: @light; + + .FormHeader { + display: flex; + margin-bottom: 2rem; + + .FormTitle { + font-size: 1.5rem; + font-weight: 800; + text-align: center; + } + } + + .FormItem { + display: grid; + grid-template-columns: 1fr 6fr; + width: 100%; + gap: 1rem; + align-items: center; + justify-content: space-between; + + .FormLabel { + font-size: 1rem; + font-weight: 800; + text-align: end; + } + + .FormInput { + color: @light; + grid-column: 2; + padding: 0.5rem; + border-radius: 0.25rem; + font-family: Montserrat; + background-color: @dark; + } + + .FormInput[type="checkbox"] { + width: auto; + margin-right: auto; + } + } + + .FormButtons { + display: flex; + margin-top: 1rem; + gap: 0.5rem; + justify-content: space-around; + + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + + [type="submit"] { + background-color: #ff4603; + } + + button { + background-color: @dark; + color: @light; + border: 0.1rem solid #ff4603; + + &:hover { + border: 0.1rem solid @light; + } + } + } + } +} diff --git a/src/pages/utils.js b/src/pages/utils.js new file mode 100644 index 0000000..4cf76b7 --- /dev/null +++ b/src/pages/utils.js @@ -0,0 +1,83 @@ +/** + * Global variables + */ +let [leftSize, rightSize] = [ + localStorage.getItem("leftSize") ?? 0, + localStorage.getItem("rightSize") ?? 0, +]; + +const parentContainer = document.getElementById("container"); + +const leftContainer = document.getElementById("left-container"); +const closeLeftButton = document.getElementById("close-left"); +const rightContainer = document.getElementById("right-container"); +const closeRightButton = document.getElementById("close-right"); + +const roomButton = document.getElementById("MenuButton"); +const chatButton = document.getElementById("ChatButton"); + +leftContainer.classList.toggle("Hidden", !leftSize); + +/** + * Calculate the layout of the home page. + * + * Initialize the layout of the home page, the left and right sidebars, the + * rest of the script adds event listeners to the elements in the layout so + * that the layout follows the mouse cursor when clicked and dragged. + * + * @returns {void} + **/ +export const calculateLayout = () => { + const parentContainerSize = `${4.5 - leftSize - rightSize}fr`; + + // left and right are reversed because of the grid layout + parentContainer.style.gridTemplateColumns = `${leftSize}fr ${parentContainerSize} ${rightSize}fr`; + leftContainer.style.opacity = leftSize; + rightContainer.style.opacity = rightSize; +}; + +closeLeftButton.addEventListener("click", () => { + leftSize = 1 - leftSize; + localStorage.setItem("leftSize", leftSize); + + calculateLayout(); + setTimeout( + () => { + leftContainer.classList.toggle("Hidden", true); + }, + leftSize ? 0 : 300, + ); +}); + +closeRightButton.addEventListener("click", () => { + rightSize = 1 - rightSize; + localStorage.setItem("rightSize", rightSize); + + calculateLayout(); + setTimeout( + () => { + rightContainer.classList.toggle("Hidden", true); + }, + rightSize ? 0 : 300, + ); +}); + +roomButton.addEventListener("click", () => { + leftSize = 1; + localStorage.setItem("leftSize", leftSize); + + calculateLayout(); + setTimeout(() => { + leftContainer.classList.toggle("Hidden", false); + }, 300); +}); + +chatButton.addEventListener("click", () => { + rightSize = 1; + localStorage.setItem("rightSize", rightSize); + + calculateLayout(); + setTimeout(() => { + rightContainer.classList.toggle("Hidden", false); + }, 300); +}); diff --git a/src/rooms/canvas/index.js b/src/rooms/canvas/index.js new file mode 100644 index 0000000..628a6a5 --- /dev/null +++ b/src/rooms/canvas/index.js @@ -0,0 +1,7 @@ +// FIXME: This file should handle the room canvas API +// Link buttons to their respective functions +// Functions may include: +// - getCanvas (get the canvas of a room and deserialize it) +// - subscribeToRoom (subscribe to the stream of a room) +// - getPixelInfo (get the pixel info of a room) +// - placePixel (place a pixel in a room) diff --git a/src/rooms/canvas/utils.js b/src/rooms/canvas/utils.js new file mode 100644 index 0000000..c5e123d --- /dev/null +++ b/src/rooms/canvas/utils.js @@ -0,0 +1,467 @@ +// This file handles the room canvas DOM manipulation +// Functions includes: +// - initCanvas (initialize the canvas) +// - renderCanvasUpdate (render a canvas update) +// - getPlacementData (get the necessary data to place a pixel) +// - toggleTooltip (toggle the tooltip and display the pixel's information) + +import $ from "jquery"; + +const canvasContainer = $("#canvas-container")?.[0]; +const canvas = $("#canvas")?.[0]; +const canvasCtx = canvas.getContext("2d"); +const selector = $("#selector")?.[0]; + +const positionTooltip = $("#position-tooltip")?.[0]; +const tooltip = $("#tooltip")?.[0]; +const colorPicker = $("#color-picker")?.[0]; +const colorWheelContainer = $("#color-wheel-container")?.[0]; +const colorWheel = $("#color-wheel")?.[0]; + +/** + * Global variables + */ +let board, palette, selectedColorIdx; +let animation; + +const zoomSpeed = 1 / 25; +let zoom = 2.5; + +let x, y; +let cx = 0; +let cy = 0; +let target = { x: 0, y: 0 }; +let isDrag = false; + +/** + * Returns the necessary data to place a pixel. + * + * Get the placement data, i.e. the color the user has selected and the + * coordinates of the pixel he is focusing on. + * + * @returns {{color: number, posX: number, posX: number}} the data + */ +export const getPlacementData = () => ({ + color: selectedColorIdx, + posX: target.x, + posY: target.y, +}); + +/** + * Get the currently focused pixel's information and display it in the tooltip. + * + * @param {boolean} [state=state] + * + * @returns {Promise} + */ +export const toggleTooltip = async (state = false) => { + tooltip.style.display = state ? "flex" : "none"; + + if (state) { + // FIXME: You should implement or call a function to get the pixel's information + // and display it. Make use of target.x and target.y to get the pixel's position. + } +}; + +/** + * Calculate the target position according to the top left corner of the canvas. + * + * @param {*} event + * + * @returns {x: number, y: number} the target position + */ +const calculateTarget = (event) => { + const rect = canvas.getBoundingClientRect(); + const scaleX = canvas.width / rect.width; + const scaleY = canvas.height / rect.height; + const canvasLeft = rect.left + window.pageXOffset; + const canvasTop = rect.top + window.pageYOffset; + + return { + x: Math.floor( + ((event?.pageX ?? window.innerWidth / 2) - canvasLeft) * scaleX, + ), + y: Math.floor( + ((event?.pageY ?? window.innerHeight / 2) - canvasTop) * scaleY, + ), + }; +}; + +/** + * Update the position tooltip according to the event. + * + * @param {*} event + * + * @returns {void} + */ +const positionUpdate = (event) => positionDisplay(calculateTarget(event)); + +/** + * Update the tooltip's position. + * + * @param {{x: number, y: number}} target + * + * @returns {void} + */ +const positionDisplay = ({ x, y }) => { + positionTooltip.innerText = `X=${x} Y=${y}`; + canvas.style.transform = `translate(${cx}px, ${cy}px) scale(${zoom})`; + + // We add the canvas.width * zoom to make cx and cy positive + let selectorX = cx + canvas.width * zoom; + let selectorY = cy + canvas.height * zoom; + + // Make odd canvas align + if (canvas.width % 2 !== 0) { + selectorX += zoom / 2; + selectorY += zoom / 2; + } + + // Find the translate + selectorX %= zoom; + selectorY %= zoom; + + // Center selector on the pixel + selectorX -= zoom / 2; + selectorY -= zoom / 2; + + selector.style.transform = `translate(${selectorX}px, ${selectorY}px) scale(${zoom})`; +}; + +// Toggle the color wheel on click on the color picker +colorPicker.addEventListener("click", () => { + const state = colorWheelContainer.style.display; + + colorWheelContainer.style.display = + !state || state === "none" ? "block" : "none"; +}); + +/** + * Transform #RRGGBB to 0xBBGGRRAA. Hexadecimal color to 32 bits integer. + * + * @param {string} hex + * + * @returns {number} the 32 bits color + */ +const transformHexTo32Bits = (hex) => { + const reverse = hex.substring(1).match(/.{2}/g).reverse().join(""); + + return parseInt(`0xFF${reverse}`, 16); +}; + +/** + * Render the canvas. + * + * @param {number[]} pixels + * @param {string[]} colors + * + * @returns {void} + */ +const renderCanvas = (pixels, colors) => { + const img = new ImageData(canvas.width, canvas.height); + const data = new Uint32Array(img.data.buffer); + + board = pixels; + palette = colors; + for (let i = 0; i < pixels.length; i++) { + data[i] = transformHexTo32Bits(colors[pixels[i]]); + } + + canvasCtx.putImageData(img, 0, 0); + canvasCtx.imageSmoothingEnabled = false; + canvas.style.imageRendering = "pixelated"; + + // Remove all the colors from the color wheel + while (colorWheel.firstChild) { + colorWheel.removeChild(colorWheel.firstChild); + } + + // Add the colors to the color wheel + for (let i = 0; i < colors.length; i++) { + const btn = document.createElement("button"); + + colorWheel.appendChild(btn); + + btn.addEventListener("click", () => { + selectedColorIdx = i; + colorPicker.style.color = colors[i]; + colorPicker.style.border = `${colors[i]} 0.1rem solid`; + }); + + btn.style.backgroundColor = colors[i]; + } +}; + +/** + * Initialize the canvas with the given room configuration and pixels. + * + * @param {*} roomConfig + * @param {number[]} pixels + * + * @returns {void} + */ +export const initCanvas = (roomConfig, pixels) => { + const canvasDimensions = roomConfig.metadata.canvasDimensions; + + canvas.width = canvasDimensions; + canvas.height = canvasDimensions; + + positionDisplay({ x: canvasDimensions / 2, y: canvasDimensions / 2 }); + selectedColorIdx = 0; + + const roomColors = roomConfig.settings.roomColors.split(","); + + colorPicker.style.color = roomColors[0]; + colorPicker.style.border = `${roomColors[0]} 0.1rem solid`; + + renderCanvas(pixels, roomColors); +}; + +/** + * Render the canvas update, i.e. update the pixel at the given coordinates. + * + * @param {string} color + * @param {number} x + * @param {number} y + * + * @returns {void} + */ +export const renderCanvasUpdate = (color, x, y) => { + const img = new ImageData(canvas.width, canvas.height); + const data = new Uint32Array(img.data.buffer); + + board[y * canvas.width + x] = color; + for (let i = 0; i < board.length; i++) { + data[i] = transformHexTo32Bits(palette[board[i]]); + } + + canvasCtx.putImageData(img, 0, 0); +}; + +/** + * Reset the values of the canvas, i.e. the zoom, the coordinates and the + * display position. + * + * @returns {void} + */ +export const resetValues = () => { + zoom = 2.5; + x = 0; + y = 0; + cx = 0; + cy = 0; + isDrag = false; + + positionDisplay({ x, y }); + colorWheelContainer.style.display = "none"; + toggleTooltip(false); +}; + +// Handle scroll on canvas +document.addEventListener("wheel", (e) => { + // Make sure we're scrolling on the canvas or the body and not the UI + if (e.target !== canvas && e.target !== canvasContainer) { + return; + } + + clearInterval(animation); + toggleTooltip(false); + + const delta = Math.sign(e.deltaY) * zoomSpeed; + const zoomFactor = 1 + delta; + const oldZoom = zoom; + const newZoom = Math.max(2.5, Math.min(40, oldZoom * zoomFactor)); + + // Get the position of the mouse relative to the canvas + const mouseX = e.clientX - window.innerWidth / 2; + const mouseY = e.clientY - window.innerHeight / 2; + + // Calculate the new center point based on the mouse position + const newCx = mouseX - (mouseX - cx) * (newZoom / oldZoom); + const newCy = mouseY - (mouseY - cy) * (newZoom / oldZoom); + + if (newZoom !== oldZoom) { + zoom = newZoom; + cx = newCx; + cy = newCy; + positionUpdate(); + } +}); + +// Handle click and drag on canvas +document.addEventListener("mousedown", (e) => { + // Make sure we're clicking on the canvas or the body and not the UI + if (e.target !== canvas && e.target !== canvasContainer) { + return; + } + + e.preventDefault(); + + // Ignore if right click + if (e.button === 2) { + return; + } + + clearInterval(animation); + + isDrag = false; + x = e.clientX; + y = e.clientY; + + document.addEventListener("mousemove", mouseMove); +}); + +// Smooth animation +function easeOutQuart(t, b, c, d) { + t /= d; + t--; + return -c * (t * t * t * t - 1) + b; +} + +// Handle when the user releases the mouse +document.addEventListener("mouseup", (e) => { + document.removeEventListener("mousemove", mouseMove); + + // Make sure we're clicking on the canvas or the body and not the UI + if (e.target !== canvas && e.target !== canvasContainer) { + return; + } + + e.preventDefault(); + + // Get the tile position + target = calculateTarget(e); + + // Make sure we're clicking on the canvas + if ( + target.x >= 0 && + target.x < canvas.width && + target.y >= 0 && + target.y < canvas.height + ) { + // We want to differentiate between a click and a drag + // If it is a click, we want to move the camera to the clicked tile + + // We wait to see if the position changed + // If it did not, we consider it a click + if (!isDrag) { + const duration = 1000; + const startZoom = zoom; + const endZoom = Math.max(15, Math.min(40, zoom)); + + // Get the position of the click in relation to the center of the screen + const clickX = e.clientX - window.innerWidth / 2; + const clickY = e.clientY - window.innerHeight / 2; + const canvaswidthzoom = canvas.width * startZoom; + const canvasheightzoom = canvas.height * startZoom; + const startx = (cx + canvaswidthzoom / 2) / startZoom; + const starty = (cy + canvasheightzoom / 2) / startZoom; + const endx = startx - clickX / startZoom; + const endy = starty - clickY / startZoom; + const endCx = endx * endZoom - (canvas.width / 2) * endZoom; + const endCy = endy * endZoom - (canvas.height / 2) * endZoom; + const startCx = cx; + const startCy = cy; + const startTime = Date.now(); + + // If the distance is small enough, we just warp to it + if ( + Math.abs(endCx - startCx) < 10 && + Math.abs(endCy - startCy) < 10 + ) { + cx = endCx; + cy = endCy; + zoom = endZoom; + canvas.style.transform = `translate(${cx}px, ${cy}px) scale(${zoom})`; + } else { + clearInterval(animation); + + animation = setInterval(() => { + const elapsed = Date.now() - startTime; + + if (elapsed >= duration) { + clearInterval(animation); + return; + } + + const t = elapsed / duration; + + zoom = easeOutQuart(t, startZoom, endZoom - startZoom, 1); + cx = easeOutQuart(t, startCx, endCx - startCx, 1); + cy = easeOutQuart(t, startCy, endCy - startCy, 1); + + positionUpdate(); + }, 10); + } + } + + // Toggle the tooltip if it is a click + toggleTooltip(!isDrag); + + // Update the position of the tooltip + positionDisplay(target); + } +}); + +// Handle mouse move +const mouseMove = (e) => { + e.preventDefault(); + + toggleTooltip(false); + positionUpdate(); + + const dx = e.clientX - x; + const dy = e.clientY - y; + + // For a big enough delta, we consider it a drag + if (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5) { + isDrag = true; + } + + x = e.clientX; + y = e.clientY; + cx += dx; + cy += dy; + + canvas.style.transform = `translate(${cx}px, ${cy}px) scale(${zoom})`; +}; + +export const displayLeaderboard = (guilds) => { + console.log(guilds); + const list = document.getElementById("leaderboard-list"); + + for (const { name, points } of guilds) { + console.log(`${name} has ${points}`); + const existingItem = [ + ...list.querySelectorAll(".LeaderboardItem"), + ].find((el) => el.dataset.guild === name); + + if (existingItem) { + existingItem.querySelector(".GuildPoints").textContent = points; + existingItem.dataset.points = points; + } else { + const li = document.createElement("li"); + + li.className = "LeaderboardItem"; + li.dataset.guild = name; + li.dataset.points = points; + li.innerHTML = ` + + ${name} + ${points} + `; + list.appendChild(li); + } + } + + const items = [...list.querySelectorAll(".LeaderboardItem")].sort( + (a, b) => b.dataset.points - a.dataset.points, + ); + + list.innerHTML = ""; + items.slice(0, 5).forEach((item, index) => { + item.className = `LeaderboardItem rank-${index + 1}`; + item.querySelector(".Rank").textContent = index + 1; + list.appendChild(item); + }); +}; diff --git a/src/rooms/chat/index.js b/src/rooms/chat/index.js new file mode 100644 index 0000000..493f142 --- /dev/null +++ b/src/rooms/chat/index.js @@ -0,0 +1,4 @@ +// FIXME: This file should handle the room's chat subscription +// Functions may include: +// - subscribeToRoomChat (subscribe to the chat of a room) +// - sendChatMessage (send a chat message) diff --git a/src/rooms/chat/utils.js b/src/rooms/chat/utils.js new file mode 100644 index 0000000..2d21ab2 --- /dev/null +++ b/src/rooms/chat/utils.js @@ -0,0 +1,6 @@ +// FIXME: This file should handle the room's chat DOM manipulation +// Link buttons to their respective functions +// Handle the chat input form and its submission +// Functions may include: +// - displayChatMessage (display a chat message in the DOM) +// - displayUserEvents (display a user event in the DOM) diff --git a/src/rooms/index.js b/src/rooms/index.js new file mode 100644 index 0000000..42b9eca --- /dev/null +++ b/src/rooms/index.js @@ -0,0 +1,10 @@ +// FIXME: This file should handle the rooms API +// Functions may include: +// - fetchRoomConfig (get the configuration of a room) +// - setCurrentRoomConfig (set the current room configuration and update the DOM accordingly) +// - getCurrentRoomConfig (get the current room configuration) +// - joinRoom (join a room by its slug) +// - listRooms (list all the rooms available) +// - createRoom (create a room) +// - updateRoom (update a room's configuration) +// - deleteRoom (delete a room) diff --git a/src/rooms/utils.js b/src/rooms/utils.js new file mode 100644 index 0000000..5e94739 --- /dev/null +++ b/src/rooms/utils.js @@ -0,0 +1,6 @@ +// FIXME: This file should handle the rooms DOM manipulation +// Link buttons to their respective functions +// Functions may include: +// - showModal (add a form modal to the DOM) +// - createRoomObject (create a room in the DOM) +// - displayRoomsList (display the rooms list in the DOM) diff --git a/src/students/index.js b/src/students/index.js new file mode 100644 index 0000000..7b6a978 --- /dev/null +++ b/src/students/index.js @@ -0,0 +1,5 @@ +// FIXME: This file should handle the students API +// Functions may include: +// - getStudent (get a student from the API by its uid or login) +// - getUserUidFromToken (get the user's uid from the token in local storage) +// - updateStudent (update the student's profile through the API) diff --git a/src/students/utils.js b/src/students/utils.js new file mode 100644 index 0000000..09bb32e --- /dev/null +++ b/src/students/utils.js @@ -0,0 +1,5 @@ +// FIXME: This file should handle the students DOM manipulation +// Link buttons to their respective functions +// Functions may include: +// - displayStudentProfile (display the student's profile in the DOM) +// - showModal (add a form modal to the DOM) diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 0000000..186f41b --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,6 @@ +// FIXME: This file should handle the authentication +// Functions may include: +// - getToken (exchanges the code for a token) +// - refreshToken (refreshes the token using the refresh_token) +// - authenticate (checks if the user is authenticated) +// - authedAPIRequest (makes an authenticated request to the API) diff --git a/src/utils/notify.js b/src/utils/notify.js new file mode 100644 index 0000000..9ac6fef --- /dev/null +++ b/src/utils/notify.js @@ -0,0 +1,64 @@ +import $ from "jquery"; +import alertHtml from "../components/notifications/index.html"; + +const iconMap = { + info: "fa-info-circle", + success: "fa-thumbs-up", + warning: "fa-exclamation-triangle", + error: "ffa fa-exclamation-circle", +}; + +/** + * Create an alert. + * + * Create an alert with the given title, message and type. + * The alert will display in the top right corner of the screen. + * This is a useful function to notify the user of any errors or warnings. + * + * @param {string} title + * @param {string} message + * @param {string} type success, warning, error + * + * @returns {void} + **/ +export const createAlert = (title, message, type) => { + const alertContainer = $("#alert-container")?.[0]; + + $.ajax({ + url: alertHtml, + success: (data) => { + const [alert] = $(data); + + // Return if the alert cannot be created, usefull when a redirect is made + if (!alertContainer || !alert || !alert.classList) { + return; + } + + // Add the type class to the alert + alert.classList.add( + `Alert${type.charAt(0).toUpperCase() + type.slice(1)}`, + ); + + // Replace values in innerHTML + alert.innerHTML = alert.innerHTML + .replace(/{{title}}/g, title) + .replace(/{{content}}/g, message) + .replace(/{{icon_classes}}/g, iconMap[type]); + + // Get the close button + const closeBtn = alert.getElementsByClassName("AlertClose")?.[0]; + + closeBtn?.addEventListener("click", () => { + alert.remove(); + }); + + // Append the alert to the container + alertContainer.append(alert); + + // Remove the alert after 5 seconds + setTimeout(() => { + alert.remove(); + }, 5000); + }, + }); +}; diff --git a/src/utils/rateLimits.js b/src/utils/rateLimits.js new file mode 100644 index 0000000..d7b11a6 --- /dev/null +++ b/src/utils/rateLimits.js @@ -0,0 +1,3 @@ +// FIXME: This file should handle the rate limits +// Functions may include: +// - displayTimer (util function to display the timer for the rate limit) diff --git a/src/utils/redirect.js b/src/utils/redirect.js new file mode 100644 index 0000000..1022c49 --- /dev/null +++ b/src/utils/redirect.js @@ -0,0 +1,4 @@ +// FIXME: This file should handle the redirection to the AUTH URL +// Functions may include: +// - createLink (construct and return the URL to redirect the user to the login page) +// - redirectToLoginPage (redirect the user to the Forge ID login page) diff --git a/src/utils/streams.js b/src/utils/streams.js new file mode 100644 index 0000000..014b042 --- /dev/null +++ b/src/utils/streams.js @@ -0,0 +1,9 @@ +// FIXME: This file should handle the sockets and the subscriptions +// Exports must include +// - initSocket (initialize the connection to the socket server) +// - socket (variable resulting of initSocket function) + +// Functions may include: +// - subscribe (subscribe to a room's stream or chat) +// - unsubscribe (unsubscribe from a room's stream or chat) +// - sendMessage (send a message to a room's chat) diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..d054241 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,44 @@ +import { resolve } from "path"; +import { defineConfig, loadEnv } from "vite"; +import dns from "dns"; + +dns.setDefaultResultOrder("verbatim"); +const root = resolve(__dirname, "src/pages/"); + +export default ({ mode }) => { + process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; + + return defineConfig({ + root, + server: { + host: process.env.VITE_HOST, + port: process.env.VITE_PORT, + proxy: { + // $VITE_URL/api* -> $VITE_API_URL/api* + "/api": { + target: process.env.VITE_API_URL, + changeOrigin: true, + }, + // $VITE_URL/socket.io* -> $VITE_API_URL/socket.io* + "/socket.io": { + target: process.env.VITE_API_URL, + changeOrigin: true, + ws: true, + }, + // $VITE_URL/auth-api* -> $VITE_AUTH_URL* + "/auth-api": { + target: process.env.VITE_AUTH_URL, + changeOrigin: true, + secure: false, + rewrite: (path) => path.replace(/^\/auth-api/, ""), + }, + }, + }, + + publicDir: resolve(__dirname, "public"), + assetsInclude: [ + "src/components/**/*.html", + "src/pages/debug/debug.html", + ], + }); +}; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..8b7336f --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1957 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@esbuild/android-arm64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.15.tgz#893ad71f3920ccb919e1757c387756a9bca2ef42" + integrity sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA== + +"@esbuild/android-arm@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.15.tgz#143e0d4e4c08c786ea410b9a7739779a9a1315d8" + integrity sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg== + +"@esbuild/android-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.15.tgz#d2d12a7676b2589864281b2274355200916540bc" + integrity sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ== + +"@esbuild/darwin-arm64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.15.tgz#2e88e79f1d327a2a7d9d06397e5232eb0a473d61" + integrity sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA== + +"@esbuild/darwin-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.15.tgz#9384e64c0be91388c57be6d3a5eaf1c32a99c91d" + integrity sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg== + +"@esbuild/freebsd-arm64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.15.tgz#2ad5a35bc52ebd9ca6b845dbc59ba39647a93c1a" + integrity sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg== + +"@esbuild/freebsd-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.15.tgz#b513a48446f96c75fda5bef470e64d342d4379cd" + integrity sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ== + +"@esbuild/linux-arm64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.15.tgz#9697b168175bfd41fa9cc4a72dd0d48f24715f31" + integrity sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA== + +"@esbuild/linux-arm@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.15.tgz#5b22062c54f48cd92fab9ffd993732a52db70cd3" + integrity sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw== + +"@esbuild/linux-ia32@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.15.tgz#eb28a13f9b60b5189fcc9e98e1024f6b657ba54c" + integrity sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q== + +"@esbuild/linux-loong64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.15.tgz#32454bdfe144cf74b77895a8ad21a15cb81cfbe5" + integrity sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ== + +"@esbuild/linux-mips64el@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.15.tgz#af12bde0d775a318fad90eb13a0455229a63987c" + integrity sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ== + +"@esbuild/linux-ppc64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.15.tgz#34c5ed145b2dfc493d3e652abac8bd3baa3865a5" + integrity sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg== + +"@esbuild/linux-riscv64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.15.tgz#87bd515e837f2eb004b45f9e6a94dc5b93f22b92" + integrity sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA== + +"@esbuild/linux-s390x@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.15.tgz#20bf7947197f199ddac2ec412029a414ceae3aa3" + integrity sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg== + +"@esbuild/linux-x64@0.17.15": + version "0.17.15" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.15.tgz" + integrity sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg== + +"@esbuild/netbsd-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.15.tgz#8da299b3ac6875836ca8cdc1925826498069ac65" + integrity sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA== + +"@esbuild/openbsd-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.15.tgz#04a1ec3d4e919714dba68dcf09eeb1228ad0d20c" + integrity sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w== + +"@esbuild/sunos-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.15.tgz#6694ebe4e16e5cd7dab6505ff7c28f9c1c695ce5" + integrity sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ== + +"@esbuild/win32-arm64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.15.tgz#1f95b2564193c8d1fee8f8129a0609728171d500" + integrity sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q== + +"@esbuild/win32-ia32@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.15.tgz#c362b88b3df21916ed7bcf75c6d09c6bf3ae354a" + integrity sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w== + +"@esbuild/win32-x64@0.17.15": + version "0.17.15" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.15.tgz#c2e737f3a201ebff8e2ac2b8e9f246b397ad19b8" + integrity sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/eslint-utils@^4.4.0": + version "4.6.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz#e4c58fdcf0696e7a5f19c30201ed43123ab15abc" + integrity sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.1": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint/config-array@^0.20.0": + version "0.20.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.0.tgz#7a1232e82376712d3340012a2f561a2764d1988f" + integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ== + dependencies: + "@eslint/object-schema" "^2.1.6" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/config-helpers@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.1.tgz#26042c028d1beee5ce2235a7929b91c52651646d" + integrity sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw== + +"@eslint/core@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.13.0.tgz#bf02f209846d3bf996f9e8009db62df2739b458c" + integrity sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.25.1": + version "9.25.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.25.1.tgz#25f5c930c2b68b5ebe7ac857f754cbd61ef6d117" + integrity sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg== + +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== + +"@eslint/plugin-kit@^0.2.8": + version "0.2.8" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz#47488d8f8171b5d4613e833313f3ce708e3525f8" + integrity sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA== + dependencies: + "@eslint/core" "^0.13.0" + levn "^0.4.1" + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.6.tgz#ee2a10eaabd1131987bf0488fd9b820174cd765e" + integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.3.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" + integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@pkgr/core@^0.2.3": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c" + integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw== + +"@socket.io/component-emitter@~3.1.0": + version "3.1.0" + resolved "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz" + integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg== + +"@types/estree@^1.0.6": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" + integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@typescript-eslint/scope-manager@8.31.0": + version "8.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz#48c7f7d729ea038e36cae0ff511e48c2412fb11c" + integrity sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw== + dependencies: + "@typescript-eslint/types" "8.31.0" + "@typescript-eslint/visitor-keys" "8.31.0" + +"@typescript-eslint/types@8.31.0": + version "8.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.0.tgz#c48e20ec47a43b72747714f49ea9f7b38a4fa6c1" + integrity sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ== + +"@typescript-eslint/typescript-estree@8.31.0": + version "8.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz#9c7f84eff6ad23d63cf086c6e93af571cd561270" + integrity sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ== + dependencies: + "@typescript-eslint/types" "8.31.0" + "@typescript-eslint/visitor-keys" "8.31.0" + debug "^4.3.4" + fast-glob "^3.3.2" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^2.0.1" + +"@typescript-eslint/utils@^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.0.tgz#6fb52471a29fdd16fc253d568c5ad4b048f78ba4" + integrity sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.31.0" + "@typescript-eslint/types" "8.31.0" + "@typescript-eslint/typescript-estree" "8.31.0" + +"@typescript-eslint/visitor-keys@8.31.0": + version "8.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz#9a1a97ed16c60d4d1e7399b41c11a6d94ebc1ce5" + integrity sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ== + dependencies: + "@typescript-eslint/types" "8.31.0" + eslint-visitor-keys "^4.2.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.14.0: + version "8.14.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" + integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + +array-includes@^3.1.6: + version "3.1.6" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" + is-string "^1.0.7" + +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +axios@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz" + integrity sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +builtins@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz" + integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== + dependencies: + semver "^7.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +copy-anything@^2.0.1: + version "2.0.6" + resolved "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== + dependencies: + is-what "^3.14.1" + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +debug@^3.2.6, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.3.1, debug@^4.3.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.3.2, debug@~4.3.1, debug@~4.3.2: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.2.0" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz" + integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +engine.io-client@~6.4.0: + version "6.4.0" + resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz" + integrity sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.0.3" + ws "~8.11.0" + xmlhttprequest-ssl "~2.0.0" + +engine.io-parser@~5.0.3: + version "5.0.6" + resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz" + integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw== + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +es-abstract@^1.19.0, es-abstract@^1.20.4: + version "1.21.2" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz" + integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== + dependencies: + array-buffer-byte-length "^1.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.2.0" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.10" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.7" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.9" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +esbuild@^0.17.5: + version "0.17.15" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.17.15.tgz" + integrity sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw== + optionalDependencies: + "@esbuild/android-arm" "0.17.15" + "@esbuild/android-arm64" "0.17.15" + "@esbuild/android-x64" "0.17.15" + "@esbuild/darwin-arm64" "0.17.15" + "@esbuild/darwin-x64" "0.17.15" + "@esbuild/freebsd-arm64" "0.17.15" + "@esbuild/freebsd-x64" "0.17.15" + "@esbuild/linux-arm" "0.17.15" + "@esbuild/linux-arm64" "0.17.15" + "@esbuild/linux-ia32" "0.17.15" + "@esbuild/linux-loong64" "0.17.15" + "@esbuild/linux-mips64el" "0.17.15" + "@esbuild/linux-ppc64" "0.17.15" + "@esbuild/linux-riscv64" "0.17.15" + "@esbuild/linux-s390x" "0.17.15" + "@esbuild/linux-x64" "0.17.15" + "@esbuild/netbsd-x64" "0.17.15" + "@esbuild/openbsd-x64" "0.17.15" + "@esbuild/sunos-x64" "0.17.15" + "@esbuild/win32-arm64" "0.17.15" + "@esbuild/win32-ia32" "0.17.15" + "@esbuild/win32-x64" "0.17.15" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-prettier@^8.8.0: + version "8.8.0" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz" + integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA== + +eslint-config-standard@^17.0.0: + version "17.0.0" + resolved "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz" + integrity sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg== + +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== + dependencies: + debug "^3.2.7" + is-core-module "^2.11.0" + resolve "^1.22.1" + +eslint-module-utils@^2.7.4: + version "2.7.4" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz" + integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== + dependencies: + debug "^3.2.7" + +eslint-plugin-es@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz" + integrity sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ== + dependencies: + eslint-utils "^2.0.0" + regexpp "^3.0.0" + +eslint-plugin-eslint-comments@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa" + integrity sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ== + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + +eslint-plugin-import@^2.25.2: + version "2.27.5" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz" + integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" + has "^1.0.3" + is-core-module "^2.11.0" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" + tsconfig-paths "^3.14.1" + +eslint-plugin-jest@^28.11.0: + version "28.11.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz#2641ecb4411941bbddb3d7cf8a8ff1163fbb510e" + integrity sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig== + dependencies: + "@typescript-eslint/utils" "^6.0.0 || ^7.0.0 || ^8.0.0" + +eslint-plugin-n@^15.0.0: + version "15.7.0" + resolved "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz" + integrity sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q== + dependencies: + builtins "^5.0.1" + eslint-plugin-es "^4.1.0" + eslint-utils "^3.0.0" + ignore "^5.1.1" + is-core-module "^2.11.0" + minimatch "^3.1.2" + resolve "^1.22.1" + semver "^7.3.8" + +eslint-plugin-prettier@^5.2.6: + version "5.2.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz#be39e3bb23bb3eeb7e7df0927cdb46e4d7945096" + integrity sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.11.0" + +eslint-plugin-promise@^6.0.0: + version "6.1.1" + resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz" + integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== + +eslint-scope@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.3.0.tgz#10cd3a918ffdd722f5f3f7b5b83db9b23c87340d" + integrity sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^1.1.0: + 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== + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.4.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + +eslint@^9.25.1: + version "9.25.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.25.1.tgz#8a7cf8dd0e6acb858f86029720adb1785ee57580" + integrity sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.20.0" + "@eslint/config-helpers" "^0.2.1" + "@eslint/core" "^0.13.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.25.1" + "@eslint/plugin-kit" "^0.2.8" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + "@types/json-schema" "^7.0.15" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.3.0" + eslint-visitor-keys "^4.2.0" + espree "^10.3.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" + integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.0" + +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + +fast-glob@^3.3.2: + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.8" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.19.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" + integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== + dependencies: + reusify "^1.0.4" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ignore@^5.0.5: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^5.1.1, ignore@^5.2.0: + version "5.2.4" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz" + integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.11.0: + version "2.11.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.10, is-typed-array@^1.1.9: + version "1.1.10" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jquery@^3.6.4: + version "3.6.4" + resolved "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz" + integrity sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +less@^4.1.3: + version "4.1.3" + resolved "https://registry.npmjs.org/less/-/less-4.1.3.tgz" + integrity sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^3.1.0" + source-map "~0.6.0" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@2.1.2, ms@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@^3.3.4: + version "3.3.6" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +needle@^3.1.0: + version "3.2.0" + resolved "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz" + integrity sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.6.3" + sax "^1.2.4" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.1: + version "3.3.1" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz" + integrity sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +object-inspect@^1.12.3, object-inspect@^1.9.0: + version "1.12.3" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.values@^1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +postcss@^8.4.21: + version "8.4.21" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz" + integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" + integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + +punycode@^2.1.0: + version "2.3.0" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +regexpp@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.22.1: + version "1.22.2" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + +rollup@^3.18.0: + version "3.20.2" + resolved "https://registry.npmjs.org/rollup/-/rollup-3.20.2.tgz" + integrity sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg== + optionalDependencies: + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.3.0: + version "6.3.0" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.0.0, semver@^7.3.8: + version "7.3.8" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@^7.6.0: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +socket.io-client@^4.6.1: + version "4.6.1" + resolved "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.6.1.tgz" + integrity sha512-5UswCV6hpaRsNg5kkEHVcbBIXEYoVbMQaHJBXJCyEQ+CiFPV1NIOY0XOFWG4XR4GZcB8Kn6AsRs/9cy9TbqVMQ== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.4.0" + socket.io-parser "~4.2.1" + +socket.io-parser@~4.2.1: + version "4.2.2" + resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz" + integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +synckit@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.4.tgz#48972326b59723fc15b8d159803cf8302b545d59" + integrity sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ== + dependencies: + "@pkgr/core" "^0.2.3" + tslib "^2.8.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" + integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== + +tsconfig-paths@^3.14.1: + version "3.14.2" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^2.3.0: + version "2.5.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + +tslib@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz" + integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== + +vite@^4.2.0: + version "4.2.1" + resolved "https://registry.npmjs.org/vite/-/vite-4.2.1.tgz" + integrity sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg== + dependencies: + esbuild "^0.17.5" + postcss "^8.4.21" + resolve "^1.22.1" + rollup "^3.18.0" + optionalDependencies: + fsevents "~2.3.2" + +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +ws@~8.11.0: + version "8.11.0" + resolved "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz" + integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== + +xmlhttprequest-ssl@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz" + integrity sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==