From b3360032ca4b74442ed98267962dee79def931a7 Mon Sep 17 00:00:00 2001 From: Park Date: Thu, 27 Nov 2025 17:24:24 +0900 Subject: [PATCH] sprint9 --- .env.example | 6 - .gitignore | 12 +- .vs/ProjectSettings.json | 3 - .vs/Splint_M_3/v17/.suo | Bin 14336 -> 0 bytes .vs/VSWorkspaceState.json | 6 - .vs/slnx.sqlite | Bin 90112 -> 0 bytes COMMIT_EDITMSG | 1 - README.md | 68 +- jest.config.js | 28 + nodemon.json | 8 +- package-lock.json | 4703 ++++++++++++----- package.json | 61 +- prisma.config.ts | 14 + prisma/dev.db | Bin 53248 -> 0 bytes .../20250801023915_init/migration.sql | 41 - prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 140 +- prisma/seed.js | 49 - render-build.sh | 7 - src/Article.js | 14 - src/ArticleService.js | 48 - src/ElectronicProduct.js | 9 - src/Product.js | 15 - src/ProductService.js | 61 - src/api/articles/articles.controller.js | 95 - src/api/articles/articles.router.js | 18 - src/api/comments/comments.controller.js | 106 - src/api/comments/comments.router.js | 25 - src/api/index.js | 12 - src/api/products/products.controller.js | 114 - src/api/products/products.router.js | 22 - src/app.js | 30 - src/app.ts | 51 +- src/controllers/authController.ts | 273 - src/controllers/commentController.ts | 380 -- src/controllers/likeController.ts | 370 -- src/controllers/post.controller.ts | 147 - src/controllers/postController.ts | 371 -- src/controllers/productController.ts | 376 -- src/controllers/user.controller.ts | 128 - src/controllers/userController.ts | 348 -- src/dto/post.dto.ts | 29 - src/dto/user.dto.ts | 29 - src/{server.js => index.ts} | 5 +- src/main.js | 31 - src/middleware/auth.ts | 110 - src/middlewares/auth.ts | 36 + src/middlewares/error-handler.middleware.js | 20 - src/middlewares/upload.middleware.js | 24 - src/middlewares/validation.middleware.js | 28 - src/repositories/articleRepository.ts | 79 + src/repositories/authRepository.ts | 37 + src/repositories/post.repository.ts | 62 - src/repositories/productRepository.ts | 85 + src/repositories/user.repository.ts | 62 - src/routes/articleRoutes.test.ts | 234 + src/routes/articleRoutes.ts | 79 + src/routes/auth.ts | 30 - src/routes/authRoutes.test.ts | 141 + src/routes/authRoutes.ts | 46 + src/routes/comments.ts | 17 - src/routes/index.ts | 11 - src/routes/post.routes.ts | 15 - src/routes/posts.ts | 54 - src/routes/productRoutes.test.ts | 259 + src/routes/productRoutes.ts | 84 + src/routes/products.ts | 54 - src/routes/user.routes.ts | 14 - src/routes/users.ts | 35 - src/services/articleService.ts | 56 + src/services/authService.ts | 35 + src/services/post.service.ts | 120 - src/services/productService.test.ts | 244 + src/services/productService.ts | 61 + src/services/user.service.ts | 124 - src/types/index.ts | 159 - src/utils/auth.ts | 108 - src/utils/jwt.ts | 23 + src/utils/prisma.ts | 10 +- src/utils/testUtils.ts | 8 + tsconfig.json | 49 +- 81 files changed, 4989 insertions(+), 5881 deletions(-) delete mode 100644 .env.example delete mode 100644 .vs/ProjectSettings.json delete mode 100644 .vs/Splint_M_3/v17/.suo delete mode 100644 .vs/VSWorkspaceState.json delete mode 100644 .vs/slnx.sqlite delete mode 100644 COMMIT_EDITMSG create mode 100644 jest.config.js create mode 100644 prisma.config.ts delete mode 100644 prisma/dev.db delete mode 100644 prisma/migrations/20250801023915_init/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 prisma/seed.js delete mode 100644 render-build.sh delete mode 100644 src/Article.js delete mode 100644 src/ArticleService.js delete mode 100644 src/ElectronicProduct.js delete mode 100644 src/Product.js delete mode 100644 src/ProductService.js delete mode 100644 src/api/articles/articles.controller.js delete mode 100644 src/api/articles/articles.router.js delete mode 100644 src/api/comments/comments.controller.js delete mode 100644 src/api/comments/comments.router.js delete mode 100644 src/api/index.js delete mode 100644 src/api/products/products.controller.js delete mode 100644 src/api/products/products.router.js delete mode 100644 src/app.js delete mode 100644 src/controllers/authController.ts delete mode 100644 src/controllers/commentController.ts delete mode 100644 src/controllers/likeController.ts delete mode 100644 src/controllers/post.controller.ts delete mode 100644 src/controllers/postController.ts delete mode 100644 src/controllers/productController.ts delete mode 100644 src/controllers/user.controller.ts delete mode 100644 src/controllers/userController.ts delete mode 100644 src/dto/post.dto.ts delete mode 100644 src/dto/user.dto.ts rename src/{server.js => index.ts} (62%) delete mode 100644 src/main.js delete mode 100644 src/middleware/auth.ts create mode 100644 src/middlewares/auth.ts delete mode 100644 src/middlewares/error-handler.middleware.js delete mode 100644 src/middlewares/upload.middleware.js delete mode 100644 src/middlewares/validation.middleware.js create mode 100644 src/repositories/articleRepository.ts create mode 100644 src/repositories/authRepository.ts delete mode 100644 src/repositories/post.repository.ts create mode 100644 src/repositories/productRepository.ts delete mode 100644 src/repositories/user.repository.ts create mode 100644 src/routes/articleRoutes.test.ts create mode 100644 src/routes/articleRoutes.ts delete mode 100644 src/routes/auth.ts create mode 100644 src/routes/authRoutes.test.ts create mode 100644 src/routes/authRoutes.ts delete mode 100644 src/routes/comments.ts delete mode 100644 src/routes/index.ts delete mode 100644 src/routes/post.routes.ts delete mode 100644 src/routes/posts.ts create mode 100644 src/routes/productRoutes.test.ts create mode 100644 src/routes/productRoutes.ts delete mode 100644 src/routes/products.ts delete mode 100644 src/routes/user.routes.ts delete mode 100644 src/routes/users.ts create mode 100644 src/services/articleService.ts create mode 100644 src/services/authService.ts delete mode 100644 src/services/post.service.ts create mode 100644 src/services/productService.test.ts create mode 100644 src/services/productService.ts delete mode 100644 src/services/user.service.ts delete mode 100644 src/types/index.ts delete mode 100644 src/utils/auth.ts create mode 100644 src/utils/jwt.ts create mode 100644 src/utils/testUtils.ts diff --git a/.env.example b/.env.example deleted file mode 100644 index fc3a713d4..000000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -DATABASE_URL="file:./dev.db" -JWT_SECRET="your-super-secret-jwt-key" -JWT_REFRESH_SECRET="your-super-secret-refresh-key" -JWT_EXPIRES_IN="1h" -JWT_REFRESH_EXPIRES_IN="7d" -PORT=3000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index ac63ad616..766ec985e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ node_modules +# Keep environment variables out of version control .env -dist -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* +/src/generated/prisma -# Uploads -uploads/ +# Claude Code +.claude/ +.claude diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json deleted file mode 100644 index f8b488856..000000000 --- a/.vs/ProjectSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CurrentProjectSetting": null -} \ No newline at end of file diff --git a/.vs/Splint_M_3/v17/.suo b/.vs/Splint_M_3/v17/.suo deleted file mode 100644 index 8b7ae36829f9c01f1b4533a48d4c8779663aefa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14336 zcmeI2+jCPz9LF~WRJ;Cp~@9vgA zzWQ#*pC*uwm<{H^@@DghuRh_+n`e!Ap0es5EH5wXmGWe}DMlPPZwmY>rq>)di#$u_ zmP8pjZ8w{$GT+qXt9u*2IrU}F{lpV$x7BHU4vYo!nK=daIdaF%%_hC9e!boFRPl@L ztKZfyeMB|tYmT4QyE7qL@l4}@va+h2+XE&3-;z7+Pnlm(q$p1^1 zG%vgi>;zr`UIktQdV$x0UBDYaAMhry8+Z%Y1H28q1H23D1^R&j;5}dv7y|YI!$1-k z0rmq2fP=s&a0u|zc*IveN`Bl|_O}b>*IhX4fG|bGvtVP89Jxg^VgKidJg!n(vN4eJ z1}4=>dx<|319(HN)1;>A7&Vfe(pfW0^fv{zl8ak0mm<-!xnw2iv>98a5T zl*(SC2G8C$@P9~uvQ?#VYwNr{uJxLJen!*&?xQhnEuuJKLsE1Rs(LW;zd2$H7@*5%l0Q-x!@8Nq<_A#zk^AZ;H*TCGL9ntQNCH4pjv<04}QsA z^KMl6E4I^`QOQr)6m-i998WZW&M#S;EFh5+;4M%tnlrSJwmK3y0)EZ$n)9RLm%otC zDn-SA0qoL6*+S99t?#kFg!ri#BPxD<=iyIbeQ9?Ua-F_2P*x(s2+oTGc&GxDXRIe! zf=?B7W1zof{X4~Xp8Q+EuX(D1$JVU8$f&Q;&sZDy{r)FqwY_9}la>j?dFs-DX#513 zizAvF%T+He%AIr&{MrvttQVF4e*BH(e-5mR{IYO;(o(&mcP@Hjz1I0J-S_0*2>&HZ zdBx6AvsK#lMF!4XvswuM6N+s`yOKAqMo-OG-u&AL|79BmED|FX8HvM(V88p?pkJ|% zxBitau0^^%Ke6%L{k%f=meSUV#V@}8;q$R}zi$ayw{Z;zWCxw-K&RhJ4?DpxJJRTg&*Hc2ei>Cu&}D{2rKKMxJ)(Q?2O~EA;xJ>Scx z%H*f`-l-j8M__=Rfg$&Qh@OUNp;;vjCgI3ZHs;5ejy!Fy!v|6DQ%otyqXm*hDb{i* zql?nL_}G9fP?}TbnT{3jgCoC5448~H1=@MZTMB-Ti4_NBkjDSYr#d=i%$=Chkz;;1 zh(5`Ze~l>(&ROz25-DQgj#rAy{N`zp=asY?U5K?bu45IljuLd_*iVyY{T5Tr5-lYg zeQ6g)YjIH%7oVlu91Dzk4k}jWJ+B;hIvncaE05Zk9My`D626sRgyuC(ynWD`7ZEG8 zej5jzbcGmBBHBlCj6Zw z-RG&U!B10Ahk~8;#yP;LM49n7ZE(@;<>Vl1F8{B;T56p0f1~_Am2`GFg->&KmQzSc z(O89=V)br{F^9Pgsa?DGYlfk76zxIu9mvimM@L3S`qM+1WPfrbGu%HjIK01qZe%b! zH#9UnGMc0HVm{!t#=w>_x%6iyu;Q# z|NVcT|4wn*a}kdbJn!=F*){SX?VSH=n(@wG8ucCZoz7jn6!n|6Z0F(Db1nOg+E*v| zgXhon&%eV*vWN38-38WM5taVKv_0c?8N{#q7&?2@+BN*vgZnWL^snXn*B|hm#;a5h zzjFggd&3!fKcvxkbpEcZtXqMo^ec)8!@qL>LZ|K8o6FKVJjwpN7%nP)-PM_O4KPUm z3jWCVKP>-u(q~ls{`21;{_gz`t%S7iu|D@dg8Yx6{a4T4qw3#7X8(y!?0@xYB;3z5 Yd(-auz1)!I56zt2`vc9my!P+^21H3p-~a#s diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json deleted file mode 100644 index 6b6114114..000000000 --- a/.vs/VSWorkspaceState.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ExpandedNodes": [ - "" - ], - "PreviewInSolutionExplorer": false -} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite deleted file mode 100644 index c0d03394001c83751e6458d141bf3ec366538841..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 zcmeI5du$uYdBAtI6e&vLj$~P)W!cuszJtx!EAt_eq7T=rOwl&avLuU=eTKsd%iX2T zJBrkil<$;WQOHiN%^i9{`UudbKyiPhIiTp_(j-m$N0AnNB@K|am!PlYniRdFP1E*@ zzJlOdq~Gj&mz1U0jU2ZhVUM^o-#7EkZ|0kwomuV{F3(k3s!*;s)})q@U4PC|0D3<{0zYtpLqa(S;zP7zVf9PJq-=3Y*QAx@IBtMp2Z4t$i;n z=jyUV{d1}Q-1ZePg|hYP##*giF4P;@I^7otuYzB#YJR0zDoeGR+Q>@sO?9>1bluCm zg8gnU^CoYbkHhD@I?(spOD{(5tn_Lg_hM1=IIouXxX^f+&l(`RXn!jTAj=P|7xnwja@yN`nIKIl$#^guJg z*+{$Sd3w*V4A!e-53|u7M_IF6tE#(ErcHJx5D4c)v&p<2d38~0sI}HA9k#t`JssQ6 zE9K0=(Ue{>I$`K^c0L8CGtC6A0`1FNXZ?ZjGcfXP+9RLC3yq@NH67!hLp)&A$Cv_n z)pp2jhq~RR62sljX;E9TluBoV{W}8&Bei8g%|spc&aZ z&j!L{W9)5J%hRyU_|xUeCOm?dXi$_nZaw}V}l`A(k8rrIK z0j@MFvig2{MSIi`IJ;4)DlVz!(KEvJqP8@6w9t|ot&MdiyA)ZXc5!oK;I3Hz%#yX=CKc*q*uaudk?FiuXQ)0 z#z&-oJ%N=LFHKEllyXW;m12q*izyi~Go{K@T*)Mo(-{fU5~?akQ*l|0O{EgzRC+op zN(osJ)l@R3Ml;EDN`>fBA}*(<(_(x&CW}*YN)e^F6cZE5bb6{(mg35kBBo1;l9E&s z;&dVv7pLNBMT8=z#Pn3W99QGU)6rxiodhG4RBTF|ij|Wflu3xm>8V6nO(j%m zIxc2P<>^c*Ba3QAf#Ra+1QeH`wx&z*^mI8HlhdM{ichD~vMi?3(zH00kyE0SQKDkJ zq?Qw@j51v+gHWkFHJzMFi_wyr0>c%s0IDL&vA9xBBo$T7WJDzskHzJrB4*M_3338S zDVGqLEFhnS&62m zq2}>82q|hRrpCcsMHQ#ixFkxk1k@uQkCkHS43JkxI3IJGX#EU&G~H~<);DUc*zqnO zBPOy9RcfiqLStoJp{*wFz`CalFxk=vnfv6-Jfdr5M z5hv&d;#_$M>$+e22RMqF;kq96y-`;-GudTt8Bpf-7SUHX9IawmEoGh`4gL+QzD}#y* zKQ6Fs8n$v8)pN2$Tsc`{;RAl=Fxz9*ZqU>ZhiO94ul-IQYGEgg=o+SpcEYfpV3uel z9N6z?#vv>17yFb*&@ANOke@jQrQ1zurw#A(Gozp-t*<*(_6_=(AyARlD^@m#2K>x% z5M8U>(0{Qp2|u3a%|eb6Ka+5#u2$>vGn1|QTD8xT;i*iIpWe0(ciP9I@kAz;gr^8q zT9(X6$j^*v)le0%C}8Ry>eoweYWKHKt~V;pHOWdH?ejAeS``i3Y{lVT$dKvr>s_^J z<~JCG8bWllA)8T$d;H8vCg_L98NlPO#1j4S$z=1UnY=#$s6az6+KfHu_cM=z61d$n z(hc3|%(U@tKXckn6Ez~#D@}oeKC0Z~uhkWGbq#tx^fEJTkkbvEyro4|FMW9aKo`^! z@|(N?&sn=st2b0r_#mrWF{##WO{&ks!*Z%t8r}at7J5HJ{)PNC`3n-^zs>(6|J(eJ z@i+NT^OyKk=xzQJ{MY#Jl0mXcK2Ls?yh=XG|2#j&`$9iUZj(!7hW`?IU+9aZ7J41B z!4DEZ0!RP}AOR$R1dsp{Kmter3A7U!<{^OBdWd_R-ER41f@*bI zauMj>@?Dl$#z(jq+rG4NG4rUzy*BlN(`VK~H%v(^&Fe!?AN9m^U+z1%3PFJmZod&>uNibxG3P>ok7f_6101!$UJ~dc+@nF~I)HZl zPcjVoFY;~jujHTLIe>pd{t8wCzC!+pJV7$#3*>jmZ<3FbUnDEglf;8x%L4XE+8g$dZM+1%qT^z>*_5J@R81fGJI{8x) z=D*Ir$-l~9<$s9(DSm?Q3BAGJ;eU(&CjZ~00M7^b75Ig~hxu0MTcI~Y*WtGUPmmb@ z26-0l1yDk_LQg!@EsDk?0VIF~kN^@u0!RP}AOR$R1nx#)%$H_6Z{l;u4*HU8$8CJ> z=n>!J?6w>ELlc^2r>*=UVZxVX@3xsgG=9JrW37#Id#nAxkwdw{{hI;wf|3l|Id;yGw=^TNB{{S z0VIF~kN^@u0!RP}AOR$R1dzbPL*OL5>ThLeH}7$bO-AYdKTE#Cz(4#T0VIF~kN^@u z0!RP}AOR$R1dsp{KmrdDfs?+Kq2MmB|LOjJh>S4gpUKzY4FJDReu?}HJlAg>-U2X3 z&X5z3gdZe;1dsp{Kmter2_OL^fCP{L5z~lxkTLq@Y)aWvf5 z<>TNngLMC&8QA+uV2MZo2_OL^fCP{L54n6E5bi2{S9p3;B7-_EK(s8LWc; zD|2(Fj9eBQ^;;E1Z8)_}8C`@n!cy*BZYejP%@u^}rbe#Ts^ zxZA_jy9m9+=4IB{Or4Qswb`tcDpguV3#(Vm(+At+>?%6}Qb#FlHe2eNh0q&CQ3|&9 zy|A3C%M$g^rTTN*SHKj?)~g$9wRX8sZ)EFqUm&~+ezmIkm1e0d)oN-ZE6F$2)ppZ$ zFY^layS>btylp-XpY!TK-)k?u7`e03t9jguMa|>9THfP6x5s&tp?uThyh%=<*@ed$ z4cf`(ww~kiIIY~BJIwv{7t^hsl`{Lg%NkSA>bzz|oBwxq^5xBS7$wb-$}Gb4yyeQ8 z+H6T{>qUVM9?SVlFp|P}MSG$c41`Zd*xN+wiDt1hzf~HT5xQZ$MzgNW=P$2VXC70= zQgU=JGtHVw0>x2rf2Uy3cCBCJJHbt z%>-v7?V{)DJ;O3suZ}&;Mt2-#&2p`(?naq5*_l8foDad=4)(if-3*jDHUCfKeY~ z3glJWA-5gsc9%*FcRQy=ZN*Y5oelQyV6d&ZgK?d}NA6tk>4S9E9;6+#ymofG9mJdL za1n3Py}62Rv)#*gF%#Lg4eYT_8^`E;ihk~4N^pN8e7;H96os?-_G#dp)NC!3nrh>g z3ezHck8XBZDg^_A3k}Ky{7e?=Cd$IQ&*envm0s+W^L|6&ed-`i6?5%`A>mn zWa~T|2#<}iw^c1q!#3kjmn)C!My#XD88Lg-)2>L>4QNMC@6=C{+Vwy43Pa}mU+MeL zzJ;Dx;06C5_~y7*A#%?@FAnSv42xpV%U5qz)aU42eNk%N)Xsnvwdp-JIA6)vmJbj`u@$|+$x2d45_ zz2EC1`Wc+6&|$)r+`1{8TUxl(R;ul}Fdtc%w-0qI?PF^B;$#Q&+#=@+li$0!ZCPmM zzwm-^{JqBo__H`^RTJvrtaalD54HviVT45%*B)PTV$+j;Cpw9UW-xJWR~R$OoR^r%hl# zbkcKeZL?C7t=C%W^SjZY7Y^}3 zSanFaRvonI;gVWbVK9)@RqGq=59Y$7-8Zb{{o6T93z2nBEi1wSS@jr?!a_lJ9oF3bo6;EwcT-d zaTRN>pO|jb$-g@Ly{WOYqkYdymR_F!yCacZoqJgVv~U|8)VA%xUWmBtq7%ZRU=1(Z z3?ARd(Z*VNa<5AnYfg{w0o?q^(>)!>(Bj>+*!D^dH$QOm=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -146,6 +133,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -209,9 +206,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -243,13 +240,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -513,58 +510,33 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -577,6 +549,43 @@ "dev": true, "license": "MIT" }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -601,6 +610,101 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@electric-sql/pglite": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", + "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@electric-sql/pglite-socket": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz", + "integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" + }, + "peerDependencies": { + "@electric-sql/pglite": "0.3.2" + } + }, + "node_modules/@electric-sql/pglite-tools": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz", + "integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==", + "devOptional": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.3.2" + } + }, + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.2.tgz", + "integrity": "sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -629,61 +733,61 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.2.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -694,117 +798,150 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.2.0", + "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -816,108 +953,125 @@ } }, "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.2.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -970,78 +1124,249 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mrleebo/prisma-ast": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz", + "integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.5.0", + "lilconfig": "^2.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@prisma/client": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", - "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", - "hasInstallScript": true, + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.0.1.tgz", + "integrity": "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g==", "license": "Apache-2.0", + "dependencies": { + "@prisma/client-runtime-utils": "7.0.1" + }, "engines": { - "node": ">=16.13" + "node": "^20.19 || ^22.12 || >=24.0" }, "peerDependencies": { - "prisma": "*" + "prisma": "*", + "typescript": ">=5.4.0" }, "peerDependenciesMeta": { "prisma": { "optional": true + }, + "typescript": { + "optional": true } } }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.0.1.tgz", + "integrity": "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.0.1.tgz", + "integrity": "sha512-MacIjXdo+hNKxPvtMzDXykIIc8HCRWoyjQ2nguJTFqLDzJBD5L6QRaANGTLOqbGtJ3sFvLRmfXhrFg3pWoK1BA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, "node_modules/@prisma/debug": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", - "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.0.1.tgz", + "integrity": "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w==", "devOptional": true, "license": "Apache-2.0" }, + "node_modules/@prisma/dev": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.13.0.tgz", + "integrity": "sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.3.2", + "@electric-sql/pglite-socket": "0.0.6", + "@electric-sql/pglite-tools": "0.2.7", + "@hono/node-server": "1.14.2", + "@mrleebo/prisma-ast": "0.12.1", + "@prisma/get-platform": "6.8.2", + "@prisma/query-plan-executor": "6.18.0", + "foreground-child": "3.3.1", + "get-port-please": "3.1.2", + "hono": "4.7.10", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.21.3", + "std-env": "3.9.0", + "valibot": "1.1.0", + "zeptomatch": "2.0.2" + } + }, "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.0.1.tgz", + "integrity": "sha512-f+D/vdKeImqUHysd5Bgv8LQ1whl4sbLepHyYMQQMK61cp4WjwJVryophleLUrfEJRpBLGTBI/7fnLVENxxMFPQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/fetch-engine": "5.22.0", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "7.0.1", + "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "@prisma/fetch-engine": "7.0.1", + "@prisma/get-platform": "7.0.1" } }, "node_modules/@prisma/engines-version": { - "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", - "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6.tgz", + "integrity": "sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==", "devOptional": true, "license": "Apache-2.0" }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", + "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1" + } + }, "node_modules/@prisma/fetch-engine": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", - "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.0.1.tgz", + "integrity": "sha512-5DnSairYIYU7dcv/9pb1KCwIRHZfhVOd34855d01lUI5QdF9rdCkMywPQbBM67YP7iCgQoEZO0/COtOMpR4i9A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1", + "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "@prisma/get-platform": "7.0.1" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", + "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "7.0.1" } }, "node_modules/@prisma/get-platform": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", - "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", + "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0" + "@prisma/debug": "6.8.2" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", + "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz", + "integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/studio-core": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.8.2.tgz", + "integrity": "sha512-/iAEWEUpTja+7gVMu1LtR2pPlvDmveAwMHdTWbDeGlT7yiv0ZTCPpmeAGdq/Y9aJ9Zj1cEGBXGRbmmNPj022PQ==", + "devOptional": true, + "license": "UNLICENSED", + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "dev": true, "license": "MIT" }, @@ -1056,19 +1381,26 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", "dev": true, "license": "MIT" }, @@ -1093,6 +1425,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1138,12 +1481,15 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/bcryptjs": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", - "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "node_modules/@types/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/body-parser": { "version": "1.19.6", @@ -1166,33 +1512,39 @@ "@types/node": "*" } }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "peerDependencies": { + "@types/express": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", "dev": true, "license": "MIT", "dependencies": { @@ -1202,16 +1554,6 @@ "@types/send": "*" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -1247,14 +1589,14 @@ } }, "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, "node_modules/@types/jsonwebtoken": { @@ -1268,6 +1610,13 @@ "@types/node": "*" } }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -1283,13 +1632,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.14.tgz", - "integrity": "sha512-gqiKWld3YIkmtrrg9zDvg9jfksZCcPywXVN7IauUGhilwGV/yOyeUsvpR796m/Jye0zUzMXPKe8Ct1B79A7N5Q==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/qs": { @@ -1306,27 +1655,48 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, "node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/serve-static": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", - "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", - "@types/send": "*" + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, "node_modules/@types/stack-utils": { @@ -1336,24 +1706,34 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, "license": "MIT", "dependencies": { @@ -1367,14 +1747,290 @@ "dev": true, "license": "MIT" }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" @@ -1423,13 +2079,16 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -1479,82 +2138,83 @@ "sprintf-js": "~1.0.2" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.2.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -1585,20 +2245,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -1609,20 +2269,28 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.3.tgz", - "integrity": "sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==", + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==", - "license": "MIT" + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -1638,38 +2306,37 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1686,9 +2353,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", - "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "dev": true, "funding": [ { @@ -1706,11 +2373,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.2", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -1719,6 +2386,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -1751,6 +2431,78 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1801,9 +2553,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001741", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", - "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -1848,7 +2600,22 @@ "node": ">=10" } }, - "node_modules/chokidar": { + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", @@ -1874,9 +2641,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { @@ -1889,10 +2656,20 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", + "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", "dev": true, "license": "MIT" }, @@ -1911,6 +2688,69 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -1923,9 +2763,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -1949,6 +2789,29 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1956,16 +2819,34 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -1985,54 +2866,39 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "cookie": "0.7.2", + "cookie-signature": "1.0.6" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.8.0" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "license": "MIT" }, "node_modules/create-require": { "version": "1.1.1", @@ -2045,7 +2911,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2056,13 +2922,29 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/dedent": { @@ -2090,6 +2972,43 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2099,15 +3018,12 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" }, "node_modules/detect-newline": { "version": "3.1.0", @@ -2119,6 +3035,17 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2129,20 +3056,10 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -2165,15 +3082,12 @@ "node": ">= 0.4" } }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "license": "MIT", - "dependencies": { - "xtend": "^4.0.0" - } + "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -2190,10 +3104,21 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.218", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", - "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", "dev": true, "license": "ISC" }, @@ -2211,12 +3136,22 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2227,9 +3162,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2266,6 +3201,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2339,108 +3290,163 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express-validator": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", - "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "node_modules/express/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", - "dependencies": { - "lodash": "^4.17.21", - "validator": "~13.12.0" - }, "engines": { - "node": ">= 8.0.0" + "node": ">=6.6.0" } }, - "node_modules/fast-json-stable-stringify": { - "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==", - "dev": true, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, "license": "MIT" }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", "dependencies": { - "bser": "2.1.1" - } - }, + "pure-rand": "^6.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fast-check/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2455,18 +3461,17 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" @@ -2486,6 +3491,81 @@ "node": ">=8" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2496,12 +3576,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fs.realpath": { @@ -2535,6 +3615,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2589,6 +3679,13 @@ "node": ">=8.0.0" } }, + "node_modules/get-port-please": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", + "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -2615,23 +3712,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2666,9 +3780,38 @@ "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==", - "dev": true, + "devOptional": true, "license": "ISC" }, + "node_modules/grammex": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.11.tgz", + "integrity": "sha512-HNwLkgRg9SqTAd1N3Uh/MnKwTBTzwBxTOPbXQ8pb0tpwydjk90k4zRE8JUn9fMUiRwKtXFZ1TWFmms3dZHN+Fg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2691,6 +3834,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2703,6 +3862,16 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.7.10.tgz", + "integrity": "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2711,21 +3880,32 @@ "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "devOptional": true, + "license": "MIT" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2737,17 +3917,28 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -2825,22 +4016,6 @@ "node": ">=8" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2894,6 +4069,19 @@ "node": ">=0.12.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -2911,7 +4099,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -2941,19 +4129,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -2970,45 +4145,20 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -3023,23 +4173,39 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", + "import-local": "^3.2.0", + "jest-cli": "30.2.0" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3051,76 +4217,75 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.2.0", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.2.0", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3132,215 +4297,211 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "chalk": "^4.1.2", + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.2.0", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", + "micromatch": "^4.0.8", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.2.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.2.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -3362,196 +4523,197 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.2.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "node_modules/jest-util": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "dependencies": { + "@jest/types": "30.2.0", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -3568,39 +4730,40 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "jest-util": "30.2.0", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -3619,6 +4782,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3627,9 +4800,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -3695,24 +4868,6 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jwa": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", @@ -3734,16 +4889,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -3754,6 +4899,16 @@ "node": ">=6" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -3778,6 +4933,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "devOptional": true, "license": "MIT" }, "node_modules/lodash.includes": { @@ -3816,12 +4972,26 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3832,6 +5002,22 @@ "yallist": "^3.0.2" } }, + "node_modules/lru.min": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", + "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -3848,19 +5034,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -3888,19 +5061,22 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -3916,6 +5092,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -3936,36 +5113,41 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4" + "node": ">=4.0.0" } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mimic-fn": { @@ -3979,16 +5161,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -4001,25 +5186,82 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4028,14 +5270,48 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4044,12 +5320,88 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4073,13 +5425,24 @@ "node": ">=8" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/nypm": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", + "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "devOptional": true, "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.2", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "tinyexec": "^1.0.1" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.16.0 || >=16.10.0" } }, "node_modules/object-inspect": { @@ -4094,6 +5457,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4110,7 +5480,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -4187,6 +5556,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4239,23 +5615,58 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "MIT" + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "devOptional": true, "license": "MIT" }, "node_modules/picocolors": { @@ -4301,22 +5712,48 @@ "node": ">=8" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "devOptional": true, + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/pretty-format": { + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", @@ -4330,39 +5767,58 @@ } }, "node_modules/prisma": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", - "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.0.1.tgz", + "integrity": "sha512-zp93MdFMSU1IHPEXbUHVUuD8wauh2BUm14OVxhxGrWJQQpXpda0rW4VSST2bci4raoldX64/wQxHKkl/wqDskQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.22.0" + "@prisma/config": "7.0.1", + "@prisma/dev": "0.13.0", + "@prisma/engines": "7.0.1", + "@prisma/studio-core": "0.8.2", + "mysql2": "3.15.3", + "postgres": "3.4.7" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": ">=16.13" + "node": "^20.19 || ^22.12 || >=24.0" }, - "optionalDependencies": { - "fsevents": "2.3.3" + "peerDependencies": { + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "typescript": { + "optional": true + } } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, "license": "MIT", "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" } }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4376,10 +5832,17 @@ "node": ">= 0.10" } }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -4394,12 +5857,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -4418,18 +5881,54 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" } }, "node_modules/react-is": { @@ -4452,6 +5951,36 @@ "node": ">=8.10.0" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/remeda": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz", + "integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.39.1" + } + }, + "node_modules/remeda/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "devOptional": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4462,27 +5991,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -4506,28 +6014,30 @@ "node": ">=8" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 4" } }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">= 18" } }, "node_modules/safe-buffer": { @@ -4556,68 +6066,67 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=10" } }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, "engines": { - "node": ">= 0.8" + "node": ">= 18" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/setprototypeof": { @@ -4630,7 +6139,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -4643,7 +6152,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -4722,18 +6231,30 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } }, "node_modules/slash": { "version": "3.0.0", @@ -4773,6 +6294,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -4787,14 +6318,21 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -4809,7 +6347,49 @@ "node": ">=10" } }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -4824,7 +6404,54 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -4837,6 +6464,16 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -4870,30 +6507,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/superagent": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.4", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.2" }, "engines": { - "node": ">=8" + "node": ">=14.18.0" } }, - "node_modules/supports-preserve-symlinks-flag": { - "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==", + "node_modules/supertest": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "dev": true, "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.3" + }, "engines": { - "node": ">= 0.4" + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/synckit" } }, "node_modules/test-exclude": { @@ -4911,6 +6586,62 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -4940,14 +6671,80 @@ "node": ">=0.6" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-jest": { + "version": "29.4.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.5.tgz", + "integrity": "sha512-HO3GyiWn2qvTQA4kTgjDcXiMwYQt68a1Y8+JuLRVpdIzm+UOLSHgl/XqR4c6nzJkq5rOkjc02O2I7P7l/Yof0Q==", "dev": true, "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, "bin": { - "tree-kill": "cli.js" + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ts-node": { @@ -4994,73 +6791,13 @@ } } }, - "node_modules/ts-node-dev": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", - "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.6", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^10.4.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" - }, - "engines": { - "node": ">=0.8.0" - }, - "peerDependencies": { - "node-notifier": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "node_modules/tsconfig/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tsconfig/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "license": "0BSD", + "optional": true }, "node_modules/type-detect": { "version": "4.0.8", @@ -5086,23 +6823,24 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true, + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5112,10 +6850,31 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -5128,10 +6887,45 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -5159,15 +6953,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -5190,13 +6975,19 @@ "node": ">=10.12.0" } }, - "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "devOptional": true, "license": "MIT", - "engines": { - "node": ">= 0.10" + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/vary": { @@ -5222,7 +7013,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -5234,7 +7025,33 @@ "node": ">= 8" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -5252,35 +7069,82 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/y18n": { @@ -5329,6 +7193,51 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -5351,6 +7260,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zeptomatch": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz", + "integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.10" + } } } } diff --git a/package.json b/package.json index fef8af8ef..4fe02fdda 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,41 @@ { - "name": "authentication-api-ts", + "name": "s_m_9", "version": "1.0.0", - "description": "Used Market and Community Board API Server", - "main": "dist/app.js", + "description": "Jest and Supertest Integration and Unit Testing Project", + "main": "dist/index.js", "scripts": { + "dev": "nodemon src/index.ts", "build": "tsc", - "typecheck": "tsc --noEmit", - "start": "node dist/app.js", - "dev": "nodemon", - "dev:ts": "ts-node -r tsconfig-paths/register src/app.ts", - "test": "echo \"Error: no test specified\" && exit 1", - "prisma:migrate": "prisma migrate dev --name init", - "prisma:deploy": "prisma migrate deploy", - "prisma:seed": "node prisma/seed.js" + "start": "node dist/index.js", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage" }, - "keywords": [], + "keywords": ["jest", "supertest", "testing", "express"], "author": "", "license": "ISC", "dependencies": { - "express": "^4.18.2", - "bcryptjs": "^2.4.3", - "jsonwebtoken": "^9.0.2", - "@prisma/client": "^5.6.0", - "cors": "^2.8.5", - "dotenv": "^16.4.5", + "@prisma/client": "^7.0.1", + "bcrypt": "^6.0.0", + "cookie-parser": "^1.4.7", + "dotenv": "^17.2.3", "express": "^5.1.0", - "express-validator": "^7.1.0", - "multer": "^1.4.5-lts.1" + "jsonwebtoken": "^9.0.2" }, "devDependencies": { - "prisma": "^5.6.0", - "typescript": "^5.2.2", - "ts-node": "^10.9.1", - "ts-node-dev": "^2.0.0", - "@types/node": "^20.8.0", - "@types/express": "^4.17.20", - "@types/bcryptjs": "^2.4.6", - "@types/jsonwebtoken": "^9.0.5", - "@types/cors": "^2.8.16", - "jest": "^29.7.0", - "@types/jest": "^29.5.7" + "@types/bcrypt": "^6.0.0", + "@types/cookie-parser": "^1.4.10", + "@types/express": "^5.0.5", + "@types/jest": "^30.0.0", + "@types/jsonwebtoken": "^9.0.10", + "@types/node": "^24.10.1", + "@types/supertest": "^6.0.3", + "jest": "^30.2.0", + "nodemon": "^3.1.11", + "prisma": "^7.0.1", + "supertest": "^7.1.4", + "ts-jest": "^29.4.5", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" } -} \ No newline at end of file +} diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 000000000..9c5e95930 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: env("DATABASE_URL"), + }, +}); diff --git a/prisma/dev.db b/prisma/dev.db deleted file mode 100644 index d7d2b392c0d36d5c96db489fe9e8d8aa4ab70944..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 zcmeI#U2ob}7{GCyrjU}PN-r`lqDV)MG6gl-R87^?yLsSLXoOJ5XwAih7&t3Rcqy1B z)2`C4?KJJX?91#M>?7=Aw>y3V3{Izv#@@w05&`@8oagxWoOA4R{H)xzEpgf#bQ-q! zBDIlHl+;&3q*AF>`B|5r;LkZ;4i4nEGWGkqpR1|QUj5=UvX=Tgb^nk1uh)KGO|KZ~ zZ0EwO`N{28O^Vd5PNRM9a5zWV$mrqgyjadT z8N{uv=HEP6$>er-mA97N`2O6QPTKVEdw%n#_H|jG$)DKE3i+qDTdK^xsXx^XQ8_Y2 zrB*JBs!=-Bj1%#Vej>D*c~q*%mJaobx$jl-QZG#Xxvbz<@{2}p+UMK!i>=FkTMkUk zRz*>cKC^VF&$iGUSPk21MP4F``hiv}o1#!N4B4N$)4;K*9ag>e3rCe>)6nD%g>N^= z;;DpJ#lexGm!4MK+Zzw3yfE|wT{c?KkA>=bi#>-Y?+pH;Ue@JgDQL$9t*AR^YE|b3 zf-~9to9{B2-1fHee%;MUFkXZ2m2fVC)9ONb_!n${dd%h5&&9`r4({dFk>_(f4(;7) zJu1JxLO8!pMQMMN{9dLrxt$&5{g+;TCzHQA`I(u?@}%T;6FpvWZavqNJ4zzmQ_=gu zJx3=!D3#N9dS3gvx>@SqKDwL9ZEY#PK5~C;gtJl7fW}u*R^E?HFAYr zWZJ5ue(T!B)n67GBl|^9KDw`6!^iUDEBu=NObf=~{B-qk>OqLe)CGkznY-zVe0}tB z|0BbXOK0N$hmrGvE_iQ6x4B$>x}3>vZYu9SA3u(qHScbCwED~7BCi5%``o%_pnhl+ zw>ym={CfWANqSsQzdJZCv5Zpr-cY^cE-vpbrk^YRXj`61m$#}dwz$|IEo1#byD4)x z`}+8Q%%=wzQ%b8#>D<# zJaG{~009ILKmY**5I_I{1Q3W%fc<}b) { - console.error(e); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); - }); diff --git a/render-build.sh b/render-build.sh deleted file mode 100644 index 12333bf53..000000000 --- a/render-build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -# exit on error -set -o errexit - -npm install -npm run prisma:deploy -npm run prisma:seed \ No newline at end of file diff --git a/src/Article.js b/src/Article.js deleted file mode 100644 index 64b1126c0..000000000 --- a/src/Article.js +++ /dev/null @@ -1,14 +0,0 @@ -// Article.js -export class Article { - constructor(title, content, writer) { - this.title = title; - this.content = content; - this.writer = writer; - this.likeCount = 0; - this.createdAt = new Date().toISOString(); // 생성 시점 저장 - } - - like() { - this.likeCount += 1; - } -} diff --git a/src/ArticleService.js b/src/ArticleService.js deleted file mode 100644 index c1188c180..000000000 --- a/src/ArticleService.js +++ /dev/null @@ -1,48 +0,0 @@ -// ArticleService.js -const ARTICLE_BASE = "https://panda-market-api-crud.vercel.app/articles"; - -export function getArticleList(page = 1, pageSize = 10, keyword = "") { - return fetch(`${ARTICLE_BASE}?page=${page}&pageSize=${pageSize}&keyword=${keyword}`) - .then(res => { - if (!res.ok) throw new Error("서버 응답 실패"); - return res.json(); - }) - .catch(err => console.error("getArticleList 실패:", err.message)); -} - -export function getArticle(id) { - return fetch(`${ARTICLE_BASE}/${id}`) - .then(res => { - if (!res.ok) throw new Error("게시글 없음"); - return res.json(); - }) - .catch(err => console.error("getArticle 실패:", err.message)); -} - -export function createArticle(articleObj) { - return fetch(ARTICLE_BASE, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(articleObj), - }) - .then(res => res.json()) - .catch(err => console.error("createArticle 실패:", err.message)); -} - -export function patchArticle(id, patchData) { - return fetch(`${ARTICLE_BASE}/${id}`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(patchData), - }) - .then(res => res.json()) - .catch(err => console.error("patchArticle 실패:", err.message)); -} - -export function deleteArticle(id) { - return fetch(`${ARTICLE_BASE}/${id}`, { - method: "DELETE", - }) - .then(res => res.json()) - .catch(err => console.error("deleteArticle 실패:", err.message)); -} diff --git a/src/ElectronicProduct.js b/src/ElectronicProduct.js deleted file mode 100644 index 28af3cf8d..000000000 --- a/src/ElectronicProduct.js +++ /dev/null @@ -1,9 +0,0 @@ -// ElectronicProduct.js -import { Product } from "./Product.js"; - -export class ElectronicProduct extends Product { - constructor(name, description, price, tags, images, manufacturer) { - super(name, description, price, tags, images); // 부모 생성자 호출 - this.manufacturer = manufacturer; // 제조사 - } -} diff --git a/src/Product.js b/src/Product.js deleted file mode 100644 index 773971900..000000000 --- a/src/Product.js +++ /dev/null @@ -1,15 +0,0 @@ -// Product.js -export class Product { - constructor(name, description, price, tags, images) { - this.name = name; // 상품명 - this.description = description; // 상품 설명 - this.price = price; // 가격 - this.tags = tags; // ["#전자제품", "#중고"] - this.images = images; // 이미지 url 배열 - this.favoriteCount = 0; // 찜하기 수 (초기값 0) - } - - favorite() { - this.favoriteCount += 1; // 찜하기 수 1 증가 - } -} diff --git a/src/ProductService.js b/src/ProductService.js deleted file mode 100644 index 7b3649698..000000000 --- a/src/ProductService.js +++ /dev/null @@ -1,61 +0,0 @@ -// ProductService.js -const BASE_URL = "https://panda-market-api-crud.vercel.app/products"; - -export async function getProductList(page = 1, pageSize = 10, keyword = "") { - try { - const response = await fetch(`${BASE_URL}?page=${page}&pageSize=${pageSize}&keyword=${keyword}`); - if (!response.ok) throw new Error(`오류: ${response.status}`); - const data = await response.json(); - return data; - } catch (error) { - console.error("getProductList 실패:", error.message); - } -} - -export async function getProduct(id) { - try { - const response = await fetch(`${BASE_URL}/${id}`); - if (!response.ok) throw new Error("데이터 없음"); - return await response.json(); - } catch (e) { - console.error("getProduct 실패:", e.message); - } -} - -export async function createProduct(productObj) { - try { - const response = await fetch(BASE_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(productObj), - }); - if (!response.ok) throw new Error("생성 실패"); - return await response.json(); - } catch (e) { - console.error("createProduct 실패:", e.message); - } -} - -export async function patchProduct(id, updatedData) { - try { - const response = await fetch(`${BASE_URL}/${id}`, { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(updatedData), - }); - return await response.json(); - } catch (e) { - console.error("patchProduct 실패:", e.message); - } -} - -export async function deleteProduct(id) { - try { - const response = await fetch(`${BASE_URL}/${id}`, { - method: "DELETE", - }); - return await response.json(); - } catch (e) { - console.error("deleteProduct 실패:", e.message); - } -} diff --git a/src/api/articles/articles.controller.js b/src/api/articles/articles.controller.js deleted file mode 100644 index 44eb04fbb..000000000 --- a/src/api/articles/articles.controller.js +++ /dev/null @@ -1,95 +0,0 @@ -const { PrismaClient } = require('@prisma/client'); -const prisma = new PrismaClient(); - -// 게시글 등록 -exports.createArticle = async (req, res, next) => { - try { - const { title, content } = req.body; - const article = await prisma.article.create({ - data: { title, content }, - }); - res.status(201).json(article); - } catch (error) { - next(error); // 에러 핸들러로 전달 - } -}; - -// 게시글 목록 조회 -exports.getArticles = async (req, res, next) => { - try { - // 쿼리 파라미터에서 검색, 정렬, 페이지네이션 정보 추출 - const { search, sort, page = 1, limit = 10 } = req.query; - const offset = (page - 1) * limit; // 페이지네이션을 위한 offset 계산 - - // 검색어가 있는 경우 where 조건 생성 - const where = search - ? { - OR: [ - { title: { contains: search, mode: 'insensitive' } }, // 제목에서 검색 (대소문자 구분 안함) - { content: { contains: search, mode: 'insensitive' } }, // 내용에서 검색 - ], - } - : {}; - - // 정렬 조건 설정 (기본은 오름차순, 'recent'일 경우 최신순) - const orderBy = sort === 'recent' ? { createdAt: 'desc' } : { createdAt: 'asc' }; - - const articles = await prisma.article.findMany({ - where, - orderBy, - skip: offset, - take: parseInt(limit), - select: { id: true, title: true, content: true, createdAt: true }, // 필요한 필드만 선택 - }); - - res.status(200).json(articles); - } catch (error) { - next(error); - } -}; - -// 게시글 상세 조회 -exports.getArticleById = async (req, res, next) => { - try { - const { id } = req.params; - const article = await prisma.article.findUnique({ - where: { id: parseInt(id) }, - select: { id: true, title: true, content: true, createdAt: true }, - }); - - if (!article) { - return res.status(404).json({ message: '게시글을 찾을 수 없습니다.' }); - } - res.status(200).json(article); - } catch (error) { - next(error); - } -}; - -// 게시글 수정 -exports.updateArticle = async (req, res, next) => { - try { - const { id } = req.params; - const { title, content } = req.body; - const updatedArticle = await prisma.article.update({ - where: { id: parseInt(id) }, - data: { title, content }, - }); - res.status(200).json(updatedArticle); - } catch (error) { - next(error); - } -}; - -// 게시글 삭제 -exports.deleteArticle = async (req, res, next) => { - try { - const { id } = req.params; - await prisma.article.delete({ - where: { id: parseInt(id) }, - }); - res.status(204).send(); // 성공적으로 삭제되었으나 컨텐츠는 없음을 알림 - } catch (error) { - next(error); - } -}; diff --git a/src/api/articles/articles.router.js b/src/api/articles/articles.router.js deleted file mode 100644 index c7e40c697..000000000 --- a/src/api/articles/articles.router.js +++ /dev/null @@ -1,18 +0,0 @@ -const express = require('express'); -const articleController = require('./articles.controller'); -const { validateArticle } = require('../../middlewares/validation.middleware'); - -const router = express.Router(); - -// /api/articles -router.route('/') - .post(validateArticle, articleController.createArticle) - .get(articleController.getArticles); - -// /api/articles/:id -router.route('/:id') - .get(articleController.getArticleById) - .patch(articleController.updateArticle) - .delete(articleController.deleteArticle); - -module.exports = router; diff --git a/src/api/comments/comments.controller.js b/src/api/comments/comments.controller.js deleted file mode 100644 index 80b71b33c..000000000 --- a/src/api/comments/comments.controller.js +++ /dev/null @@ -1,106 +0,0 @@ -const { PrismaClient } = require('@prisma/client'); -const prisma = new PrismaClient(); - -// 상품에 댓글 등록 -exports.createProductComment = async (req, res, next) => { - try { - const { productId } = req.params; - const { content } = req.body; - const comment = await prisma.comment.create({ - data: { - content, - productId: parseInt(productId) - }, - }); - res.status(201).json(comment); - } catch (error) { - next(error); // 에러 핸들러로 전달 - } -}; - -// 게시글에 댓글 등록 -exports.createArticleComment = async (req, res, next) => { - try { - const { articleId } = req.params; - const { content } = req.body; - const comment = await prisma.comment.create({ - data: { - content, - articleId: parseInt(articleId) - }, - }); - res.status(201).json(comment); - } catch (error) { - next(error); - } -}; - -// 상품 댓글 목록 조회 (커서 기반 페이지네이션) -exports.getProductComments = async (req, res, next) => { - try { - const { productId } = req.params; - const { cursor, limit = 10 } = req.query; - - const comments = await prisma.comment.findMany({ - where: { productId: parseInt(productId) }, - take: parseInt(limit), // 가져올 댓글 수 - skip: cursor ? 1 : 0, // 커서가 있으면 1개 건너뛰기 - cursor: cursor ? { id: parseInt(cursor) } : undefined, // 커서 위치 지정 - orderBy: { createdAt: 'desc' }, // 최신순으로 정렬 - select: { id: true, content: true, createdAt: true }, // 필요한 필드만 선택 - }); - - res.status(200).json(comments); - } catch (error) { - next(error); - } -}; - -// 게시글 댓글 목록 조회 (커서 기반 페이지네이션) -exports.getArticleComments = async (req, res, next) => { - try { - const { articleId } = req.params; - const { cursor, limit = 10 } = req.query; - - const comments = await prisma.comment.findMany({ - where: { articleId: parseInt(articleId) }, - take: parseInt(limit), - skip: cursor ? 1 : 0, - cursor: cursor ? { id: parseInt(cursor) } : undefined, - orderBy: { createdAt: 'desc' }, - select: { id: true, content: true, createdAt: true }, - }); - - res.status(200).json(comments); - } catch (error) { - next(error); - } -}; - -// 댓글 수정 -exports.updateComment = async (req, res, next) => { - try { - const { commentId } = req.params; - const { content } = req.body; - const updatedComment = await prisma.comment.update({ - where: { id: parseInt(commentId) }, - data: { content }, - }); - res.status(200).json(updatedComment); - } catch (error) { - next(error); - } -}; - -// 댓글 삭제 -exports.deleteComment = async (req, res, next) => { - try { - const { commentId } = req.params; - await prisma.comment.delete({ - where: { id: parseInt(commentId) }, - }); - res.status(204).send(); // 성공적으로 삭제되었으나 컨텐츠는 없음을 알림 - } catch (error) { - next(error); - } -}; diff --git a/src/api/comments/comments.router.js b/src/api/comments/comments.router.js deleted file mode 100644 index c19fee163..000000000 --- a/src/api/comments/comments.router.js +++ /dev/null @@ -1,25 +0,0 @@ -const express = require('express'); -const commentController = require('./comments.controller'); -const { validateComment } = require('../../middlewares/validation.middleware'); - -const router = express.Router(); - -// --- Product Comments --- -// /api/products/:productId/comments -router.route('/products/:productId/comments') - .post(validateComment, commentController.createProductComment) - .get(commentController.getProductComments); - -// --- Article Comments --- -// /api/articles/:articleId/comments -router.route('/articles/:articleId/comments') - .post(validateComment, commentController.createArticleComment) - .get(commentController.getArticleComments); - -// --- General Comment Actions --- -// /api/comments/:commentId -router.route('/comments/:commentId') - .patch(validateComment, commentController.updateComment) - .delete(commentController.deleteComment); - -module.exports = router; diff --git a/src/api/index.js b/src/api/index.js deleted file mode 100644 index 0979d9e78..000000000 --- a/src/api/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const express = require('express'); -const productsRouter = require('./products/products.router'); -const articlesRouter = require('./articles/articles.router'); -const commentsRouter = require('./comments/comments.router'); - -const router = express.Router(); - -router.use('/products', productsRouter); -router.use('/articles', articlesRouter); -router.use('/', commentsRouter); // /products/:productId/comments, /articles/:articleId/comments - -module.exports = router; diff --git a/src/api/products/products.controller.js b/src/api/products/products.controller.js deleted file mode 100644 index 5e17566cf..000000000 --- a/src/api/products/products.controller.js +++ /dev/null @@ -1,114 +0,0 @@ -const { PrismaClient } = require('@prisma/client'); -const prisma = new PrismaClient(); - -// 상품 등록 -exports.createProduct = async (req, res, next) => { - try { - const { name, description, price, tags } = req.body; - const product = await prisma.product.create({ - data: { - name, - description, - price: parseInt(price), // 가격을 정수로 변환 - tags, - }, - }); - res.status(201).json(product); - } catch (error) { - next(error); // 에러 핸들러로 전달 - } -}; - -// 상품 목록 조회 -exports.getProducts = async (req, res, next) => { - try { - // 쿼리 파라미터에서 검색, 정렬, 페이지네이션 정보 추출 - const { search, sort, page = 1, limit = 10 } = req.query; - const offset = (page - 1) * limit; // 페이지네이션을 위한 offset 계산 - - // 검색어가 있는 경우 where 조건 생성 - const where = search - ? { - OR: [ - { name: { contains: search, mode: 'insensitive' } }, // 이름에서 검색 (대소문자 구분 안함) - { description: { contains: search, mode: 'insensitive' } }, // 설명에서 검색 - ], - } - : {}; - - // 정렬 조건 설정 (기본은 오름차순, 'recent'일 경우 최신순) - const orderBy = sort === 'recent' ? { createdAt: 'desc' } : { createdAt: 'asc' }; - - const products = await prisma.product.findMany({ - where, - orderBy, - skip: offset, - take: parseInt(limit), - select: { id: true, name: true, price: true, createdAt: true }, // 필요한 필드만 선택 - }); - - res.status(200).json(products); - } catch (error) { - next(error); - } -}; - -// 상품 상세 조회 -exports.getProductById = async (req, res, next) => { - try { - const { id } = req.params; - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) }, - select: { id: true, name: true, description: true, price: true, tags: true, createdAt: true }, - }); - - if (!product) { - return res.status(404).json({ message: '상품을 찾을 수 없습니다.' }); - } - res.status(200).json(product); - } catch (error) { - next(error); - } -}; - -// 상품 수정 -exports.updateProduct = async (req, res, next) => { - try { - const { id } = req.params; - const { name, description, price, tags } = req.body; - const updatedProduct = await prisma.product.update({ - where: { id: parseInt(id) }, - data: { - name, - description, - price: price ? parseInt(price) : undefined, // 가격이 있는 경우에만 업데이트 - tags, - }, - }); - res.status(200).json(updatedProduct); - } catch (error) { - next(error); - } -}; - -// 상품 삭제 -exports.deleteProduct = async (req, res, next) => { - try { - const { id } = req.params; - await prisma.product.delete({ - where: { id: parseInt(id) }, - }); - res.status(204).send(); // 성공적으로 삭제되었으나 컨텐츠는 없음을 알림 - } catch (error) { - next(error); - } -}; - -// 이미지 업로드 -exports.uploadImage = (req, res) => { - if (!req.file) { - return res.status(400).json({ message: '이미지가 제공되지 않았습니다.' }); - } - const imageUrl = `/uploads/${req.file.filename}`; - res.status(201).json({ imageUrl }); -}; diff --git a/src/api/products/products.router.js b/src/api/products/products.router.js deleted file mode 100644 index 822111189..000000000 --- a/src/api/products/products.router.js +++ /dev/null @@ -1,22 +0,0 @@ -const express = require('express'); -const productController = require('./products.controller'); -const { validateProduct } = require('../../middlewares/validation.middleware'); -const upload = require('../../middlewares/upload.middleware'); - -const router = express.Router(); - -// /api/products -router.route('/') - .post(validateProduct, productController.createProduct) - .get(productController.getProducts); - -// /api/products/:id -router.route('/:id') - .get(productController.getProductById) - .patch(productController.updateProduct) - .delete(productController.deleteProduct); - -// /api/products/upload-image -router.post('/upload-image', upload.single('image'), productController.uploadImage); - -module.exports = router; diff --git a/src/app.js b/src/app.js deleted file mode 100644 index 3d854396c..000000000 --- a/src/app.js +++ /dev/null @@ -1,30 +0,0 @@ -require('dotenv').config(); -const express = require('express'); -const cors = require('cors'); -const path = require('path'); -const errorHandler = require('./middlewares/error-handler.middleware'); -const apiRouter = require('./api'); - -const app = express(); - -// CORS 설정 -app.use(cors({ origin: process.env.CORS_ORIGIN || '*' })); - -// Middleware -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - -// 정적 파일 제공 (이미지 업로드용) -app.use('/uploads', express.static(path.join(__dirname, '../uploads'))); - -// API 라우터 -app.use('/api', apiRouter); - -app.get('/', (req, res) => { - res.send('Welcome to the API'); -}); - -// 전역 에러 핸들러 -app.use(errorHandler); - -module.exports = app; diff --git a/src/app.ts b/src/app.ts index d342cf8c2..42c39e473 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,47 +1,20 @@ -import express, { Application, Request, Response, NextFunction } from "express"; -import cors from "cors"; -import routes from "./routes"; +import express from 'express'; +import cookieParser from 'cookie-parser'; +import authRoutes from './routes/authRoutes'; +import productRoutes from './routes/productRoutes'; +import articleRoutes from './routes/articleRoutes'; -const app: Application = express(); -const PORT = process.env.PORT || 3000; +const app = express(); -// 미들웨어 설정 -app.use(cors()); app.use(express.json()); -app.use(express.urlencoded({ extended: true })); +app.use(cookieParser()); -// API 라우트 -app.use("/api", routes); +app.use('/auth', authRoutes); +app.use('/products', productRoutes); +app.use('/articles', articleRoutes); -// 헬스 체크 엔드포인트 -app.get("/health", (req: Request, res: Response) => { - res.json({ - status: "OK", - - message: "서버가 정상적으로 작동 중입니다.", - timestamp: new Date().toISOString(), - }); -}); - -// 에러 핸들러 -app.use((err: Error, req: Request, res: Response, next: NextFunction) => { - console.error("서버 에러:", err.stack); - res.status(500).json({ - error: "서버에서 오류가 발생했습니다.", - message: err.message, - }); -}); - -// 404 핸들러 -app.use((req: Request, res: Response) => { - res.status(404).json({ - error: "요청하신 리소스를 찾을 수 없습니다.", - path: req.path, - }); -}); - -app.listen(PORT, () => { - console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`); +app.get('/health', (req, res) => { + res.status(200).json({ status: 'ok' }); }); export default app; diff --git a/src/controllers/authController.ts b/src/controllers/authController.ts deleted file mode 100644 index 63c3067d4..000000000 --- a/src/controllers/authController.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { Request, Response } from "express"; -import { body, validationResult, ValidationChain } from "express-validator"; -import prisma from "../utils/prisma"; -import { - hashPassword, - comparePassword, - generateAccessToken, - generateRefreshToken, - verifyRefreshToken, - saveRefreshToken, - deleteRefreshToken, - cleanupExpiredTokens, -} from "../utils/auth"; -import { - SignupRequest, - LoginRequest, - RefreshTokenRequest, - TokenResponse, - ApiResponse, -} from "../types"; - -// 회원가입 유효성 검사 규칙 -export const signupValidation: ValidationChain[] = [ - body("email") - .isEmail() - .withMessage("올바른 이메일 형식이 아닙니다.") - .normalizeEmail(), - body("nickname") - .isLength({ min: 2, max: 20 }) - .withMessage("닉네임은 2~20자 사이여야 합니다.") - .matches(/^[가-힣a-zA-Z0-9_]+$/) - .withMessage("닉네임은 한글, 영문, 숫자, 언더스코어만 사용 가능합니다."), - body("password") - .isLength({ min: 6 }) - .withMessage("비밀번호는 최소 6자 이상이어야 합니다.") - .matches(/^(?=.*[a-zA-Z])(?=.*\d)/) - .withMessage("비밀번호는 영문과 숫자를 포함해야 합니다."), -]; - -// 로그인 유효성 검사 규칙 -export const loginValidation: ValidationChain[] = [ - body("email") - .isEmail() - .withMessage("올바른 이메일 형식이 아닙니다.") - .normalizeEmail(), - body("password").notEmpty().withMessage("비밀번호를 입력해주세요."), -]; - -// 회원가입 -export const signup = async ( - req: Request, - res: Response -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: "유효성 검사 실패", - details: errors.array(), - }); - return; - } - - const { email, nickname, password } = res.locals.user; - - // 이메일 중복 검사 - const existingUserByEmail = await prisma.user.findUnique({ - where: { email }, - }); - - if (existingUserByEmail) { - res.status(409).json({ - error: "이미 사용 중인 이메일입니다.", - }); - return; - } - - // 닉네임 중복 검사 - const existingUserByNickname = await prisma.user.findFirst({ - where: { nickname }, - }); - - if (existingUserByNickname) { - res.status(409).json({ - error: "이미 사용 중인 닉네임입니다.", - }); - return; - } - - // 비밀번호 해싱 - const hashedPassword = await hashPassword(password); - - // 사용자 생성 - const user = await prisma.user.create({ - data: { - email, - nickname, - password: hashedPassword, - }, - select: { - id: true, - email: true, - nickname: true, - image: true, - createdAt: true, - updatedAt: true, - }, - }); - - res.status(201).json({ - message: "회원가입이 완료되었습니다.", - data: { user }, - }); - } catch (error) { - console.error("회원가입 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - }); - } -}; - -// 로그인 -export const login = async ( - req: Request, - res: Response -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: "유효성 검사 실패", - details: errors.array(), - } as any); - return; - } - - const { email, password } = res.locals.user; - - // 사용자 조회 - const user = await prisma.user.findUnique({ - where: { email }, - }); - - if (!user) { - res.status(401).json({ - error: "이메일 또는 비밀번호가 올바르지 않습니다.", - } as any); - return; - } - - // 비밀번호 검증 - const isPasswordValid = await comparePassword(password, user.password); - if (!isPasswordValid) { - res.status(401).json({ - error: "이메일 또는 비밀번호가 올바르지 않습니다.", - } as any); - return; - } - - // 토큰 생성 - const accessToken = generateAccessToken(user.id); - const refreshToken = generateRefreshToken(user.id); - - // Refresh Token을 DB에 저장 - await saveRefreshToken(user.id, refreshToken); - - // 만료된 토큰 정리 - await cleanupExpiredTokens(); - - // 사용자 정보 (비밀번호 제외) - const { password: _, ...userWithoutPassword } = user; - - res.status(200).json({ - message: "로그인이 완료되었습니다.", - user: userWithoutPassword, - accessToken, - refreshToken, - }); - } catch (error) { - console.error("로그인 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - } as any); - } -}; - -// 토큰 갱신 -export const refreshTokens = async ( - req: Request, - res: Response -): Promise => { - try { - const { refreshToken } = res.locals.user; - - if (!refreshToken) { - res.status(401).json({ - error: "Refresh token이 필요합니다.", - } as any); - return; - } - - // Refresh Token 검증 - const decoded = verifyRefreshToken(refreshToken); - if (!decoded) { - res.status(401).json({ - error: "유효하지 않은 refresh token입니다.", - } as any); - return; - } - - // DB에서 Refresh Token 확인 - const storedToken = await prisma.refreshToken.findUnique({ - where: { token: refreshToken }, - include: { user: true }, - }); - - if (!storedToken || storedToken.expiresAt < new Date()) { - res.status(401).json({ - error: "만료된 refresh token입니다.", - } as any); - return; - } - - // 새로운 토큰 생성 - const newAccessToken = generateAccessToken(storedToken.userId); - const newRefreshToken = generateRefreshToken(storedToken.userId); - - // 기존 Refresh Token 삭제 및 새로운 Token 저장 - await deleteRefreshToken(refreshToken); - await saveRefreshToken(storedToken.userId, newRefreshToken); - - // 사용자 정보 (비밀번호 제외) - const { password: _, ...userWithoutPassword } = storedToken.user; - - res.status(200).json({ - message: "토큰이 갱신되었습니다.", - user: userWithoutPassword, - accessToken: newAccessToken, - refreshToken: newRefreshToken, - }); - } catch (error) { - console.error("토큰 갱신 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - } as any); - } -}; - -// 로그아웃 -export const logout = async ( - req: Request, - res: Response -): Promise => { - try { - const { refreshToken } = res.locals.user; - - if (refreshToken) { - // Refresh Token을 DB에서 삭제 - await deleteRefreshToken(refreshToken); - } - - res.status(200).json({ - message: "로그아웃이 완료되었습니다.", - }); - } catch (error) { - console.error("로그아웃 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - }); - } -}; diff --git a/src/controllers/commentController.ts b/src/controllers/commentController.ts deleted file mode 100644 index 08a67b265..000000000 --- a/src/controllers/commentController.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { Response } from 'express'; -import { body, validationResult, ValidationChain } from 'express-validator'; -import prisma from '../utils/prisma'; -import { - AuthenticatedRequest, - CommentRequest, - PaginationQuery, - PaginatedResponse, - Comment, - ApiResponse -} from '../types'; - -// 댓글 생성/수정 유효성 검사 -export const commentValidation: ValidationChain[] = [ - body('content') - .isLength({ min: 1, max: 1000 }) - .withMessage('댓글 내용은 1~1000자 사이여야 합니다.') -]; - -// 상품 댓글 생성 (로그인 필수) -export const createProductComment = async ( - req: AuthenticatedRequest & { body: CommentRequest; params: { id: string } }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { id } = req.params; // 상품 ID - const { content } = req.body; - - // 상품 존재 확인 - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) } - }); - - if (!product) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - }); - return; - } - - // 댓글 생성 - const comment = await prisma.comment.create({ - data: { - content, - authorId: req.user.id, - productId: parseInt(id) - }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - } - }); - - res.status(201).json({ - message: '댓글이 성공적으로 등록되었습니다.', - data: { comment } - }); - - } catch (error) { - console.error('상품 댓글 생성 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 게시글 댓글 생성 (로그인 필수) -export const createPostComment = async ( - req: AuthenticatedRequest & { body: CommentRequest; params: { id: string } }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { id } = req.params; // 게시글 ID - const { content } = req.body; - - // 게시글 존재 확인 - const post = await prisma.post.findUnique({ - where: { id: parseInt(id) } - }); - - if (!post) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - }); - return; - } - - // 댓글 생성 - const comment = await prisma.comment.create({ - data: { - content, - authorId: req.user.id, - postId: parseInt(id) - }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - } - }); - - res.status(201).json({ - message: '댓글이 성공적으로 등록되었습니다.', - data: { comment } - }); - - } catch (error) { - console.error('게시글 댓글 생성 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 상품 댓글 목록 조회 -export const getProductComments = async ( - req: AuthenticatedRequest & { params: { id: string }; query: PaginationQuery }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 상품 ID - const { page = '1', limit = '10' } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); - - // 상품 존재 확인 - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) } - }); - - if (!product) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - } as any); - return; - } - - // 댓글 목록 조회 - const comments = await prisma.comment.findMany({ - where: { productId: parseInt(id) }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - }, - orderBy: { createdAt: 'desc' }, - skip: parseInt(skip.toString()), - take: parseInt(limit) - }); - - // 총 개수 조회 - const totalCount = await prisma.comment.count({ - where: { productId: parseInt(id) } - }); - - const result = { - data: comments, - pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), - totalCount, - hasNext: skip + comments.length < totalCount - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('상품 댓글 목록 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - } as any); - } -}; - -// 게시글 댓글 목록 조회 -export const getPostComments = async ( - req: AuthenticatedRequest & { params: { id: string }; query: PaginationQuery }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 게시글 ID - const { page = '1', limit = '10' } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); - - // 게시글 존재 확인 - const post = await prisma.post.findUnique({ - where: { id: parseInt(id) } - }); - - if (!post) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - } as any); - return; - } - - // 댓글 목록 조회 - const comments = await prisma.comment.findMany({ - where: { postId: parseInt(id) }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - }, - orderBy: { createdAt: 'desc' }, - skip: parseInt(skip.toString()), - take: parseInt(limit) - }); - - // 총 개수 조회 - const totalCount = await prisma.comment.count({ - where: { postId: parseInt(id) } - }); - - const result = { - data: comments, - pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), - totalCount, - hasNext: skip + comments.length < totalCount - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('게시글 댓글 목록 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - } as any); - } -}; - -// 댓글 수정 (작성자만 가능) -export const updateComment = async ( - req: AuthenticatedRequest & { body: CommentRequest; params: { id: string } }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { id } = req.params; // 댓글 ID - const { content } = req.body; - - // 댓글 존재 및 권한 확인 - const existingComment = await prisma.comment.findUnique({ - where: { id: parseInt(id) } - }); - - if (!existingComment) { - res.status(404).json({ - error: '댓글을 찾을 수 없습니다.' - }); - return; - } - - if (existingComment.authorId !== req.user.id) { - res.status(403).json({ - error: '댓글을 수정할 권한이 없습니다.' - }); - return; - } - - // 댓글 수정 - const updatedComment = await prisma.comment.update({ - where: { id: parseInt(id) }, - data: { content }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - } - }); - - res.status(200).json({ - message: '댓글이 성공적으로 수정되었습니다.', - data: { comment: updatedComment } - }); - - } catch (error) { - console.error('댓글 수정 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 댓글 삭제 (작성자만 가능) -export const deleteComment = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response -): Promise => { - try { - const { id } = req.params; // 댓글 ID - - // 댓글 존재 및 권한 확인 - const existingComment = await prisma.comment.findUnique({ - where: { id: parseInt(id) } - }); - - if (!existingComment) { - res.status(404).json({ - error: '댓글을 찾을 수 없습니다.' - }); - return; - } - - if (existingComment.authorId !== req.user.id) { - res.status(403).json({ - error: '댓글을 삭제할 권한이 없습니다.' - }); - return; - } - - // 댓글 삭제 - await prisma.comment.delete({ - where: { id: parseInt(id) } - }); - - res.status(200).json({ - message: '댓글이 성공적으로 삭제되었습니다.' - }); - - } catch (error) { - console.error('댓글 삭제 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; \ No newline at end of file diff --git a/src/controllers/likeController.ts b/src/controllers/likeController.ts deleted file mode 100644 index 8884f36bf..000000000 --- a/src/controllers/likeController.ts +++ /dev/null @@ -1,370 +0,0 @@ -import { Response } from 'express'; -import prisma from '../utils/prisma'; -import { - AuthenticatedRequest, - PaginationQuery, - PaginatedResponse, - ProductLike, - PostLike, - ApiResponse -} from '../types'; - -// 상품 좋아요/취소 토글 -export const toggleProductLike = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 상품 ID - const userId = req.user.id; - - // 상품 존재 확인 - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) } - }); - - if (!product) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - }); - return; - } - - // 이미 좋아요했는지 확인 - const existingLike = await prisma.productLike.findUnique({ - where: { - userId_productId: { - userId, - productId: parseInt(id) - } - } - }); - - if (existingLike) { - // 좋아요 취소 - await prisma.productLike.delete({ - where: { id: existingLike.id } - }); - - res.status(200).json({ - message: '좋아요가 취소되었습니다.', - data: { isLiked: false } - }); - } else { - // 좋아요 추가 - await prisma.productLike.create({ - data: { - userId, - productId: parseInt(id) - } - }); - - res.status(200).json({ - message: '좋아요가 추가되었습니다.', - data: { isLiked: true } - }); - } - - } catch (error) { - console.error('상품 좋아요 토글 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 게시글 좋아요/취소 토글 -export const togglePostLike = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 게시글 ID - const userId = req.user.id; - - // 게시글 존재 확인 - const post = await prisma.post.findUnique({ - where: { id: parseInt(id) } - }); - - if (!post) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - }); - return; - } - - // 이미 좋아요했는지 확인 - const existingLike = await prisma.postLike.findUnique({ - where: { - userId_postId: { - userId, - postId: parseInt(id) - } - } - }); - - if (existingLike) { - // 좋아요 취소 - await prisma.postLike.delete({ - where: { id: existingLike.id } - }); - - res.status(200).json({ - message: '좋아요가 취소되었습니다.', - data: { isLiked: false } - }); - } else { - // 좋아요 추가 - await prisma.postLike.create({ - data: { - userId, - postId: parseInt(id) - } - }); - - res.status(200).json({ - message: '좋아요가 추가되었습니다.', - data: { isLiked: true } - }); - } - - } catch (error) { - console.error('게시글 좋아요 토글 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 상품 좋아요 상태 조회 -export const getProductLikeStatus = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 상품 ID - const userId = req.user.id; - - // 상품 존재 확인 - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) }, - include: { - _count: { - select: { likes: true } - }, - likes: { - where: { userId }, - select: { id: true } - } - } - }); - - if (!product) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - }); - return; - } - - res.status(200).json({ - data: { - productId: parseInt(id), - likesCount: product._count.likes, - isLiked: product.likes.length > 0 - } - }); - - } catch (error) { - console.error('상품 좋아요 상태 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 게시글 좋아요 상태 조회 -export const getPostLikeStatus = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 게시글 ID - const userId = req.user.id; - - // 게시글 존재 확인 - const post = await prisma.post.findUnique({ - where: { id: parseInt(id) }, - include: { - _count: { - select: { likes: true } - }, - likes: { - where: { userId }, - select: { id: true } - } - } - }); - - if (!post) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - }); - return; - } - - res.status(200).json({ - data: { - postId: parseInt(id), - likesCount: post._count.likes, - isLiked: post.likes.length > 0 - } - }); - - } catch (error) { - console.error('게시글 좋아요 상태 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 상품 좋아요한 사용자 목록 조회 -export const getProductLikes = async ( - req: AuthenticatedRequest & { params: { id: string }; query: PaginationQuery }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 상품 ID - const { page = '1', limit = '10' } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); - - // 상품 존재 확인 - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) } - }); - - if (!product) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - } as any); - return; - } - - // 좋아요한 사용자 목록 조회 - const likes = await prisma.productLike.findMany({ - where: { productId: parseInt(id) }, - include: { - user: { - select: { - id: true, - nickname: true, - image: true - } - } - }, - orderBy: { createdAt: 'desc' }, - skip: parseInt(skip.toString()), - take: parseInt(limit) - }); - - // 총 개수 조회 - const totalCount = await prisma.productLike.count({ - where: { productId: parseInt(id) } - }); - - const result = { - data: likes.map(like => ({ - id: like.id, - userId: like.userId, - productId: like.productId, - createdAt: like.createdAt, - user: like.user - })), - pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), - totalCount, - hasNext: skip + likes.length < totalCount - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('상품 좋아요 사용자 목록 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - } as any); - } -}; - -// 게시글 좋아요한 사용자 목록 조회 -export const getPostLikes = async ( - req: AuthenticatedRequest & { params: { id: string }; query: PaginationQuery }, - res: Response> -): Promise => { - try { - const { id } = req.params; // 게시글 ID - const { page = '1', limit = '10' } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); - - // 게시글 존재 확인 - const post = await prisma.post.findUnique({ - where: { id: parseInt(id) } - }); - - if (!post) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - } as any); - return; - } - - // 좋아요한 사용자 목록 조회 - const likes = await prisma.postLike.findMany({ - where: { postId: parseInt(id) }, - include: { - user: { - select: { - id: true, - nickname: true, - image: true - } - } - }, - orderBy: { createdAt: 'desc' }, - skip: parseInt(skip.toString()), - take: parseInt(limit) - }); - - // 총 개수 조회 - const totalCount = await prisma.postLike.count({ - where: { postId: parseInt(id) } - }); - - const result = { - data: likes.map(like => ({ - id: like.id, - userId: like.userId, - postId: like.postId, - createdAt: like.createdAt, - user: like.user - })), - pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), - totalCount, - hasNext: skip + likes.length < totalCount - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('게시글 좋아요 사용자 목록 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - } as any); - } -}; \ No newline at end of file diff --git a/src/controllers/post.controller.ts b/src/controllers/post.controller.ts deleted file mode 100644 index 6b7f83dbb..000000000 --- a/src/controllers/post.controller.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Request, Response } from 'express'; -import { PostService } from '@/services/post.service'; -import { CreatePostDto, UpdatePostDto } from '@/dto/post.dto'; - -export class PostController { - private postService: PostService; - - constructor() { - this.postService = new PostService(); - } - - // 모든 포스트 조회 - getAllPosts = async (req: Request, res: Response): Promise => { - try { - const result = await this.postService.getAllPosts(); - res.json(result); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; - - // ID로 포스트 조회 - getPostById = async (req: Request, res: Response): Promise => { - try { - const id = parseInt(req.params.id); - - if (isNaN(id)) { - res.status(400).json({ error: '유효하지 않은 포스트 ID입니다.' }); - return; - } - - const post = await this.postService.getPostById(id); - - if (!post) { - res.status(404).json({ error: '포스트를 찾을 수 없습니다.' }); - return; - } - - res.json(post); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; - - // 사용자 ID로 포스트 조회 - getPostsByUserId = async (req: Request, res: Response): Promise => { - try { - const userId = parseInt(req.params.userId); - - if (isNaN(userId)) { - res.status(400).json({ error: '유효하지 않은 사용자 ID입니다.' }); - return; - } - - const result = await this.postService.getPostsByUserId(userId); - res.json(result); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; - - // 포스트 생성 - createPost = async (req: Request, res: Response): Promise => { - try { - const createPostDto: CreatePostDto = req.body; - - if (!createPostDto.title || !createPostDto.content || !createPostDto.userId) { - res.status(400).json({ error: '필수 필드가 누락되었습니다.' }); - return; - } - - const post = await this.postService.createPost(createPostDto); - res.status(201).json(post); - } catch (error) { - if (error instanceof Error && error.message.includes('존재하지 않는')) { - res.status(404).json({ error: error.message }); - return; - } - - res.status(400).json({ - error: error instanceof Error ? error.message : '잘못된 요청입니다.' - }); - } - }; - - // 포스트 수정 - updatePost = async (req: Request, res: Response): Promise => { - try { - const id = parseInt(req.params.id); - - if (isNaN(id)) { - res.status(400).json({ error: '유효하지 않은 포스트 ID입니다.' }); - return; - } - - const updatePostDto: UpdatePostDto = req.body; - const post = await this.postService.updatePost(id, updatePostDto); - - if (!post) { - res.status(404).json({ error: '포스트를 찾을 수 없습니다.' }); - return; - } - - res.json(post); - } catch (error) { - if (error instanceof Error && error.message.includes('존재하지 않는')) { - res.status(404).json({ error: error.message }); - return; - } - - res.status(400).json({ - error: error instanceof Error ? error.message : '잘못된 요청입니다.' - }); - } - }; - - // 포스트 삭제 - deletePost = async (req: Request, res: Response): Promise => { - try { - const id = parseInt(req.params.id); - - if (isNaN(id)) { - res.status(400).json({ error: '유효하지 않은 포스트 ID입니다.' }); - return; - } - - const deleted = await this.postService.deletePost(id); - - if (!deleted) { - res.status(404).json({ error: '포스트를 찾을 수 없습니다.' }); - return; - } - - res.status(204).send(); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; -} \ No newline at end of file diff --git a/src/controllers/postController.ts b/src/controllers/postController.ts deleted file mode 100644 index fe2a39471..000000000 --- a/src/controllers/postController.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { Response } from 'express'; -import { body, validationResult, ValidationChain } from 'express-validator'; -import prisma from '../utils/prisma'; -import { - AuthenticatedRequest, - OptionalAuthRequest, - PostRequest, - PaginationQuery, - PaginatedResponse, - Post, - ApiResponse -} from '../types'; - -// 게시글 생성/수정 유효성 검사 -export const postValidation: ValidationChain[] = [ - body('title') - .isLength({ min: 1, max: 100 }) - .withMessage('제목은 1~100자 사이여야 합니다.'), - body('content') - .isLength({ min: 1 }) - .withMessage('내용을 입력해주세요.'), - body('image') - .optional() - .isURL() - .withMessage('올바른 이미지 URL 형식이 아닙니다.') -]; - -// 게시글 목록 조회 (로그인 선택적) -export const getPosts = async ( - req: OptionalAuthRequest & { query: PaginationQuery }, - res: Response> -): Promise => { - try { - const { page = '1', limit = '10', search } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); - - // 검색 조건 - const where = search ? { - OR: [ - { title: { contains: search } }, - { content: { contains: search } } - ] - } : {}; - - const posts = await prisma.post.findMany({ - where, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - }, - // 로그인한 사용자가 있다면 좋아요 상태도 조회 - likes: req.user ? { - where: { userId: req.user.id }, - select: { id: true } - } : false - }, - orderBy: { createdAt: 'desc' }, - skip: parseInt(skip.toString()), - take: parseInt(limit) - }); - - // 총 개수 조회 - const totalCount = await prisma.post.count({ where }); - - const result = { - data: posts.map(post => ({ - ...post, - likesCount: post._count.likes, - commentsCount: post._count.comments, - isLiked: req.user ? (post.likes as any[]).length > 0 : false, - _count: undefined as any, - likes: undefined as any - })), - pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), - totalCount, - hasNext: skip + posts.length < totalCount - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('게시글 목록 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - } as any); - } -}; - -// 게시글 상세 조회 (로그인 선택적) -export const getPost = async ( - req: OptionalAuthRequest & { params: { id: string } }, - res: Response> -): Promise => { - try { - const { id } = req.params; - - const post = await prisma.post.findUnique({ - where: { id: parseInt(id) }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - }, - // 로그인한 사용자가 있다면 좋아요 상태도 조회 - likes: req.user ? { - where: { userId: req.user.id }, - select: { id: true } - } : false, - // 댓글 목록 - comments: { - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - }, - orderBy: { createdAt: 'desc' } - } - } - }); - - if (!post) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - }); - return; - } - - const result = { - data: { - post: { - ...post, - likesCount: post._count.likes, - commentsCount: post._count.comments, - isLiked: req.user ? (post.likes as any[]).length > 0 : false, - _count: undefined as any, - likes: undefined as any - } - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('게시글 상세 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 게시글 생성 (로그인 필수) -export const createPost = async ( - req: AuthenticatedRequest & { body: PostRequest }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { title, content, image } = req.body; - - const post = await prisma.post.create({ - data: { - title, - content, - image, - authorId: req.user.id - }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - } - } - }); - - res.status(201).json({ - message: '게시글이 성공적으로 등록되었습니다.', - data: { - post: { - ...post, - likesCount: post._count.likes, - commentsCount: post._count.comments, - isLiked: false, - _count: undefined as any - } - } - }); - - } catch (error) { - console.error('게시글 생성 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 게시글 수정 (작성자만 가능) -export const updatePost = async ( - req: AuthenticatedRequest & { body: PostRequest; params: { id: string } }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { id } = req.params; - const { title, content, image } = req.body; - - // 게시글 존재 및 권한 확인 - const existingPost = await prisma.post.findUnique({ - where: { id: parseInt(id) } - }); - - if (!existingPost) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - }); - return; - } - - if (existingPost.authorId !== req.user.id) { - res.status(403).json({ - error: '게시글을 수정할 권한이 없습니다.' - }); - return; - } - - // 게시글 수정 - const updatedPost = await prisma.post.update({ - where: { id: parseInt(id) }, - data: { - title, - content, - image - }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - }, - likes: { - where: { userId: req.user.id }, - select: { id: true } - } - } - }); - - res.status(200).json({ - message: '게시글이 성공적으로 수정되었습니다.', - data: { - post: { - ...updatedPost, - likesCount: updatedPost._count.likes, - commentsCount: updatedPost._count.comments, - isLiked: (updatedPost.likes as any[]).length > 0, - _count: undefined as any, - likes: undefined as any - } - } - }); - - } catch (error) { - console.error('게시글 수정 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 게시글 삭제 (작성자만 가능) -export const deletePost = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response -): Promise => { - try { - const { id } = req.params; - - // 게시글 존재 및 권한 확인 - const existingPost = await prisma.post.findUnique({ - where: { id: parseInt(id) } - }); - - if (!existingPost) { - res.status(404).json({ - error: '게시글을 찾을 수 없습니다.' - }); - return; - } - - if (existingPost.authorId !== req.user.id) { - res.status(403).json({ - error: '게시글을 삭제할 권한이 없습니다.' - }); - return; - } - - // 게시글 삭제 (관련된 댓글, 좋아요도 CASCADE로 자동 삭제) - await prisma.post.delete({ - where: { id: parseInt(id) } - }); - - res.status(200).json({ - message: '게시글이 성공적으로 삭제되었습니다.' - }); - - } catch (error) { - console.error('게시글 삭제 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; \ No newline at end of file diff --git a/src/controllers/productController.ts b/src/controllers/productController.ts deleted file mode 100644 index 3aa239b3c..000000000 --- a/src/controllers/productController.ts +++ /dev/null @@ -1,376 +0,0 @@ -import { Response } from 'express'; -import { body, validationResult, ValidationChain } from 'express-validator'; -import prisma from '../utils/prisma'; -import { - AuthenticatedRequest, - OptionalAuthRequest, - ProductRequest, - PaginationQuery, - PaginatedResponse, - Product, - ApiResponse -} from '../types'; - -// 상품 생성/수정 유효성 검사 -export const productValidation: ValidationChain[] = [ - body('title') - .isLength({ min: 1, max: 100 }) - .withMessage('제목은 1~100자 사이여야 합니다.'), - body('content') - .isLength({ min: 1 }) - .withMessage('내용을 입력해주세요.'), - body('price') - .isInt({ min: 0 }) - .withMessage('가격은 0 이상의 정수여야 합니다.'), - body('image') - .optional() - .isURL() - .withMessage('올바른 이미지 URL 형식이 아닙니다.') -]; - -// 상품 목록 조회 (로그인 선택적) -export const getProducts = async ( - req: OptionalAuthRequest & { query: PaginationQuery }, - res: Response> -): Promise => { - try { - const { page = '1', limit = '10', search } = req.query; - const skip = (parseInt(page) - 1) * parseInt(limit); - - // 검색 조건 - const where = search ? { - OR: [ - { title: { contains: search } }, - { content: { contains: search } } - ] - } : {}; - - const products = await prisma.product.findMany({ - where, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - }, - // 로그인한 사용자가 있다면 좋아요 상태도 조회 - likes: req.user ? { - where: { userId: req.user.id }, - select: { id: true } - } : false - }, - orderBy: { createdAt: 'desc' }, - skip: parseInt(skip.toString()), - take: parseInt(limit) - }); - - // 총 개수 조회 - const totalCount = await prisma.product.count({ where }); - - const result = { - data: products.map(product => ({ - ...product, - likesCount: product._count.likes, - commentsCount: product._count.comments, - isLiked: req.user ? (product.likes as any[]).length > 0 : false, - _count: undefined as any, - likes: undefined as any - })), - pagination: { - currentPage: parseInt(page), - totalPages: Math.ceil(totalCount / parseInt(limit)), - totalCount, - hasNext: skip + products.length < totalCount - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('상품 목록 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - } as any); - } -}; - -// 상품 상세 조회 (로그인 선택적) -export const getProduct = async ( - req: OptionalAuthRequest & { params: { id: string } }, - res: Response> -): Promise => { - try { - const { id } = req.params; - - const product = await prisma.product.findUnique({ - where: { id: parseInt(id) }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - }, - // 로그인한 사용자가 있다면 좋아요 상태도 조회 - likes: req.user ? { - where: { userId: req.user.id }, - select: { id: true } - } : false, - // 댓글 목록 - comments: { - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - } - }, - orderBy: { createdAt: 'desc' } - } - } - }); - - if (!product) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - }); - return; - } - - const result = { - data: { - product: { - ...product, - likesCount: product._count.likes, - commentsCount: product._count.comments, - isLiked: req.user ? (product.likes as any[]).length > 0 : false, - _count: undefined as any, - likes: undefined as any - } - } - }; - - res.status(200).json(result); - - } catch (error) { - console.error('상품 상세 조회 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 상품 생성 (로그인 필수) -export const createProduct = async ( - req: AuthenticatedRequest & { body: ProductRequest }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { title, content, price, image } = req.body; - - const product = await prisma.product.create({ - data: { - title, - content, - price: parseInt(price.toString()), - image, - authorId: req.user.id - }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - } - } - }); - - res.status(201).json({ - message: '상품이 성공적으로 등록되었습니다.', - data: { - product: { - ...product, - likesCount: product._count.likes, - commentsCount: product._count.comments, - isLiked: false, - _count: undefined as any - } - } - }); - - } catch (error) { - console.error('상품 생성 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 상품 수정 (작성자만 가능) -export const updateProduct = async ( - req: AuthenticatedRequest & { body: ProductRequest; params: { id: string } }, - res: Response> -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: '유효성 검사 실패', - details: errors.array() - }); - return; - } - - const { id } = req.params; - const { title, content, price, image } = req.body; - - // 상품 존재 및 권한 확인 - const existingProduct = await prisma.product.findUnique({ - where: { id: parseInt(id) } - }); - - if (!existingProduct) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - }); - return; - } - - if (existingProduct.authorId !== req.user.id) { - res.status(403).json({ - error: '상품을 수정할 권한이 없습니다.' - }); - return; - } - - // 상품 수정 - const updatedProduct = await prisma.product.update({ - where: { id: parseInt(id) }, - data: { - title, - content, - price: parseInt(price.toString()), - image - }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true - } - }, - _count: { - select: { - likes: true, - comments: true - } - }, - likes: { - where: { userId: req.user.id }, - select: { id: true } - } - } - }); - - res.status(200).json({ - message: '상품이 성공적으로 수정되었습니다.', - data: { - product: { - ...updatedProduct, - likesCount: updatedProduct._count.likes, - commentsCount: updatedProduct._count.comments, - isLiked: (updatedProduct.likes as any[]).length > 0, - _count: undefined as any, - likes: undefined as any - } - } - }); - - } catch (error) { - console.error('상품 수정 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; - -// 상품 삭제 (작성자만 가능) -export const deleteProduct = async ( - req: AuthenticatedRequest & { params: { id: string } }, - res: Response -): Promise => { - try { - const { id } = req.params; - - // 상품 존재 및 권한 확인 - const existingProduct = await prisma.product.findUnique({ - where: { id: parseInt(id) } - }); - - if (!existingProduct) { - res.status(404).json({ - error: '상품을 찾을 수 없습니다.' - }); - return; - } - - if (existingProduct.authorId !== req.user.id) { - res.status(403).json({ - error: '상품을 삭제할 권한이 없습니다.' - }); - return; - } - - // 상품 삭제 (관련된 댓글, 좋아요도 CASCADE로 자동 삭제) - await prisma.product.delete({ - where: { id: parseInt(id) } - }); - - res.status(200).json({ - message: '상품이 성공적으로 삭제되었습니다.' - }); - - } catch (error) { - console.error('상품 삭제 오류:', error); - res.status(500).json({ - error: '서버 내부 오류가 발생했습니다.' - }); - } -}; \ No newline at end of file diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts deleted file mode 100644 index a197e2378..000000000 --- a/src/controllers/user.controller.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { Request, Response } from 'express'; -import { UserService } from '@/services/user.service'; -import { CreateUserDto, UpdateUserDto } from '@/dto/user.dto'; - -export class UserController { - private userService: UserService; - - constructor() { - this.userService = new UserService(); - } - - // 모든 사용자 조회 - getAllUsers = async (req: Request, res: Response): Promise => { - try { - const result = await this.userService.getAllUsers(); - res.json(result); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; - - // ID로 사용자 조회 - getUserById = async (req: Request, res: Response): Promise => { - try { - const id = parseInt(req.params.id); - - if (isNaN(id)) { - res.status(400).json({ error: '유효하지 않은 사용자 ID입니다.' }); - return; - } - - const user = await this.userService.getUserById(id); - - if (!user) { - res.status(404).json({ error: '사용자를 찾을 수 없습니다.' }); - return; - } - - res.json(user); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; - - // 사용자 생성 - createUser = async (req: Request, res: Response): Promise => { - try { - const createUserDto: CreateUserDto = req.body; - - if (!createUserDto.name || !createUserDto.email || createUserDto.age === undefined) { - res.status(400).json({ error: '필수 필드가 누락되었습니다.' }); - return; - } - - const user = await this.userService.createUser(createUserDto); - res.status(201).json(user); - } catch (error) { - if (error instanceof Error && error.message.includes('이미 존재하는')) { - res.status(409).json({ error: error.message }); - return; - } - - res.status(400).json({ - error: error instanceof Error ? error.message : '잘못된 요청입니다.' - }); - } - }; - - // 사용자 수정 - updateUser = async (req: Request, res: Response): Promise => { - try { - const id = parseInt(req.params.id); - - if (isNaN(id)) { - res.status(400).json({ error: '유효하지 않은 사용자 ID입니다.' }); - return; - } - - const updateUserDto: UpdateUserDto = req.body; - const user = await this.userService.updateUser(id, updateUserDto); - - if (!user) { - res.status(404).json({ error: '사용자를 찾을 수 없습니다.' }); - return; - } - - res.json(user); - } catch (error) { - if (error instanceof Error && error.message.includes('이미 존재하는')) { - res.status(409).json({ error: error.message }); - return; - } - - res.status(400).json({ - error: error instanceof Error ? error.message : '잘못된 요청입니다.' - }); - } - }; - - // 사용자 삭제 - deleteUser = async (req: Request, res: Response): Promise => { - try { - const id = parseInt(req.params.id); - - if (isNaN(id)) { - res.status(400).json({ error: '유효하지 않은 사용자 ID입니다.' }); - return; - } - - const deleted = await this.userService.deleteUser(id); - - if (!deleted) { - res.status(404).json({ error: '사용자를 찾을 수 없습니다.' }); - return; - } - - res.status(204).send(); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : '서버 오류가 발생했습니다.' - }); - } - }; -} \ No newline at end of file diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts deleted file mode 100644 index 74ae957ec..000000000 --- a/src/controllers/userController.ts +++ /dev/null @@ -1,348 +0,0 @@ -import { Request, Response } from "express"; -import { body, validationResult, ValidationChain } from "express-validator"; -import prisma from "../utils/prisma"; -import { hashPassword, comparePassword } from "../utils/auth"; -import { - UpdateProfileRequest, - PaginatedResponse, - Product, - ApiResponse, - UserResponse, -} from "../types"; - -// 비밀번호 변경 유효성 검사 -export const changePasswordValidation: ValidationChain[] = [ - body("currentPassword") - .notEmpty() - .withMessage("현재 비밀번호를 입력해주세요."), - body("newPassword") - .isLength({ min: 6 }) - .withMessage("새 비밀번호는 최소 6자 이상이어야 합니다.") - .matches(/^(?=.*[a-zA-Z])(?=.*\d)/) - .withMessage("새 비밀번호는 영문과 숫자를 포함해야 합니다."), -]; - -// 프로필 업데이트 유효성 검사 -export const updateProfileValidation: ValidationChain[] = [ - body("nickname") - .optional() - .isLength({ min: 2, max: 20 }) - .withMessage("닉네임은 2~20자 사이여야 합니다.") - .matches(/^[가-힣a-zA-Z0-9_]+$/) - .withMessage("닉네임은 한글, 영문, 숫자, 언더스코어만 사용 가능합니다."), - body("image") - .optional() - .isURL() - .withMessage("올바른 이미지 URL 형식이 아닙니다."), -]; - -// 내 정보 조회 -export const getMyProfile = async ( - req: Request, - res: Response -): Promise => { - try { - const user = await prisma.user.findUnique({ - where: { id: res.locals.user.id }, - select: { - id: true, - email: true, - nickname: true, - image: true, - createdAt: true, - updatedAt: true, - }, - }); - - if (!user) { - res.status(404).json({ - error: "사용자를 찾을 수 없습니다.", - }); - return; - } - - res.status(200).json({ data: { user } }); - } catch (error) { - console.error("내 정보 조회 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - }); - } -}; - -// 내 정보 수정 -export const updateMyProfile = async ( - req: Request, - res: Response -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: "유효성 검사 실패", - details: errors.array(), - }); - return; - } - - const { nickname, image } = req.body; - const updateData: Partial = {}; - - // 닉네임 변경 요청이 있는 경우 중복 검사 - if (nickname) { - const existingUser = await prisma.user.findFirst({ - where: { - nickname, - NOT: { id: res.locals.user.id }, - }, - }); - - if (existingUser) { - res.status(409).json({ - error: "이미 사용 중인 닉네임입니다.", - }); - return; - } - - updateData.nickname = nickname; - } - - // 이미지 URL 업데이트 - if (image !== undefined) { - updateData.image = image; - } - - // 업데이트할 데이터가 없는 경우 - if (Object.keys(updateData).length === 0) { - res.status(400).json({ - error: "업데이트할 정보가 없습니다.", - }); - return; - } - - // 사용자 정보 업데이트 - const updatedUser = await prisma.user.update({ - where: { id: res.locals.user.id }, - data: updateData, - select: { - id: true, - email: true, - nickname: true, - image: true, - createdAt: true, - updatedAt: true, - }, - }); - - res.status(200).json({ - message: "프로필이 성공적으로 업데이트되었습니다.", - data: { user: updatedUser }, - }); - } catch (error) { - console.error("프로필 업데이트 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - }); - } -}; - -// 비밀번호 변경 -export const changePassword = async ( - req: Request, - res: Response -): Promise => { - try { - // 유효성 검사 결과 확인 - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ - error: "유효성 검사 실패", - details: errors.array(), - }); - return; - } - - const { currentPassword, newPassword } = req.body; - - // 현재 사용자 정보 조회 - const user = await prisma.user.findUnique({ - where: { id: res.locals.user.id }, - }); - - if (!user) { - res.status(404).json({ - error: "사용자를 찾을 수 없습니다.", - }); - return; - } - - // 현재 비밀번호 확인 - const isCurrentPasswordValid = await comparePassword( - currentPassword, - user.password - ); - if (!isCurrentPasswordValid) { - res.status(401).json({ - error: "현재 비밀번호가 올바르지 않습니다.", - }); - return; - } - - // 새 비밀번호가 현재 비밀번호와 같은지 확인 - const isSamePassword = await comparePassword(newPassword, user.password); - if (isSamePassword) { - res.status(400).json({ - error: "새 비밀번호는 현재 비밀번호와 달라야 합니다.", - }); - return; - } - - // 새 비밀번호 해싱 - const hashedNewPassword = await hashPassword(newPassword); - - // 비밀번호 업데이트 - await prisma.user.update({ - where: { id: res.locals.user.id }, - data: { password: hashedNewPassword }, - }); - - res.status(200).json({ - message: "비밀번호가 성공적으로 변경되었습니다.", - }); - } catch (error) { - console.error("비밀번호 변경 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - }); - } -}; - -// 내가 등록한 상품 목록 조회 -export const getMyProducts = async ( - req: Request, - res: Response> -): Promise => { - try { - const { page = "1", limit = "10" } = req.query; - const skip = (parseInt(page as string) - 1) * parseInt(limit as string); - - const products = await prisma.product.findMany({ - where: { authorId: res.locals.user.id }, - include: { - author: { - select: { - id: true, - nickname: true, - image: true, - }, - }, - _count: { - select: { - likes: true, - comments: true, - }, - }, - }, - orderBy: { createdAt: "desc" }, - skip: parseInt(skip.toString()), - take: parseInt(limit as string), - }); - - // 총 개수 조회 - const totalCount = await prisma.product.count({ - where: { authorId: res.locals.user.id }, - }); - - const result: PaginatedResponse = { - data: products.map((product) => ({ - ...product, - likesCount: product._count.likes, - commentsCount: product._count.comments, - image: product.image || undefined, - author: product.author as UserResponse, - _count: undefined as any, - })), - pagination: { - currentPage: parseInt(page as string), - totalPages: Math.ceil(totalCount / parseInt(limit as string)), - totalCount, - hasNext: skip + products.length < totalCount, - }, - }; - - res.status(200).json(result); - } catch (error) { - console.error("내 상품 목록 조회 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - } as any); - } -}; - -// 내가 좋아요한 상품 목록 조회 -export const getMyLikedProducts = async ( - req: Request, - res: Response> -): Promise => { - try { - const { page = "1", limit = "10" } = req.query; - const skip = (parseInt(page as string) - 1) * parseInt(limit as string); - - const likedProducts = await prisma.productLike.findMany({ - where: { userId: res.locals.user.id }, - include: { - product: { - include: { - author: { - select: { - id: true, - nickname: true, - image: true, - }, - }, - _count: { - select: { - likes: true, - comments: true, - }, - }, - }, - }, - }, - orderBy: { createdAt: "desc" }, - skip: parseInt(skip.toString()), - take: parseInt(limit as string), - }); - - // 총 개수 조회 - const totalCount = await prisma.productLike.count({ - where: { userId: res.locals.user.id }, - }); - - const result: PaginatedResponse = { - data: likedProducts.map((like) => ({ - ...like.product, - likesCount: like.product._count.likes, - commentsCount: like.product._count.comments, - image: like.product.image || undefined, - author: like.product.author as UserResponse, - isLiked: true, - _count: undefined as any, - })), - pagination: { - currentPage: parseInt(page as string), - totalPages: Math.ceil(totalCount / parseInt(limit as string)), - totalCount, - hasNext: skip + likedProducts.length < totalCount, - }, - }; - - res.status(200).json(result); - } catch (error) { - console.error("내가 좋아요한 상품 목록 조회 오류:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - } as any); - } -}; diff --git a/src/dto/post.dto.ts b/src/dto/post.dto.ts deleted file mode 100644 index 1a0320eea..000000000 --- a/src/dto/post.dto.ts +++ /dev/null @@ -1,29 +0,0 @@ -// 포스트 생성 요청 DTO -export interface CreatePostDto { - title: string; - content: string; - userId: number; -} - -// 포스트 수정 요청 DTO -export interface UpdatePostDto { - title?: string; - content?: string; - userId?: number; -} - -// 포스트 응답 DTO -export interface PostResponseDto { - id: number; - title: string; - content: string; - userId: number; -} - -// 포스트 목록 응답 DTO -export interface PostsResponseDto { - posts: PostResponseDto[]; - total: number; - page?: number; - limit?: number; -} \ No newline at end of file diff --git a/src/dto/user.dto.ts b/src/dto/user.dto.ts deleted file mode 100644 index eb4dbcf08..000000000 --- a/src/dto/user.dto.ts +++ /dev/null @@ -1,29 +0,0 @@ -// 사용자 생성 요청 DTO -export interface CreateUserDto { - name: string; - email: string; - age: number; -} - -// 사용자 수정 요청 DTO -export interface UpdateUserDto { - name?: string; - email?: string; - age?: number; -} - -// 사용자 응답 DTO -export interface UserResponseDto { - id: number; - name: string; - email: string; - age: number; -} - -// 사용자 목록 응답 DTO -export interface UsersResponseDto { - users: UserResponseDto[]; - total: number; - page?: number; - limit?: number; -} \ No newline at end of file diff --git a/src/server.js b/src/index.ts similarity index 62% rename from src/server.js rename to src/index.ts index 9ced3af19..f36e98ac5 100644 --- a/src/server.js +++ b/src/index.ts @@ -1,4 +1,7 @@ -const app = require('./app'); +import dotenv from 'dotenv'; +import app from './app'; + +dotenv.config(); const PORT = process.env.PORT || 3000; diff --git a/src/main.js b/src/main.js deleted file mode 100644 index 27ea72fe2..000000000 --- a/src/main.js +++ /dev/null @@ -1,31 +0,0 @@ -import { getProductList } from './ProductService.js'; -import { Product } from './Product.js'; -import { ElectronicProduct } from './ElectronicProduct.js'; - -async function main() { - try { - const data = await getProductList(1, 10, ""); - - if (!data || !data.items || data.items.length === 0) { - console.log("❗ 상품 데이터가 비어 있습니다."); - return; - } - - const products = data.items.map((item) => { - const { name, description, price, tags, images, manufacturer } = item; - - if (tags.includes("전자제품")) { - return new ElectronicProduct(name, description, price, tags, images, manufacturer); - } else { - return new Product(name, description, price, tags, images); - } - }); - - console.log("✅ 생성된 상품 객체 리스트:"); - console.log(products); - } catch (e) { - console.log("🚫 에러 발생:", e.message); - } -} - -main(); // ← 반드시 호출해줘야 실행됨 diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts deleted file mode 100644 index 3553d1ee4..000000000 --- a/src/middleware/auth.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import { verifyAccessToken } from "../utils/auth"; -import prisma from "../utils/prisma"; -import { - AuthenticatedRequest, - OptionalAuthRequest, - UserResponse, -} from "../types"; - -// 인증이 필요한 API를 위한 미들웨어 -export const authenticateToken = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - try { - const authHeader = req.headers["authorization"] as string; - const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN - - if (!token) { - res.status(401).json({ - error: "Access token이 필요합니다.", - }); - return; - } - - // 토큰 검증 - const decoded = verifyAccessToken(token); - if (!decoded) { - res.status(401).json({ - error: "유효하지 않은 토큰입니다.", - }); - return; - } - - // 유저 존재 여부 확인 - const user = await prisma.user.findUnique({ - where: { id: decoded.userId }, - select: { - id: true, - email: true, - nickname: true, - image: true, - createdAt: true, - updatedAt: true, - }, - }); - - if (!user) { - res.status(401).json({ - error: "존재하지 않는 사용자입니다.", - }); - return; - } - - // res.locals 객체에 사용자 정보 추가 - res.locals.user = user; - next(); - } catch (error) { - console.error("Auth middleware error:", error); - res.status(500).json({ - error: "서버 내부 오류가 발생했습니다.", - }); - } -}; - -// 선택적 인증 미들웨어 (로그인한 사용자 정보가 있으면 추가, 없어도 통과) -export const optionalAuth = async ( - req: Request, - res: Response, - next: NextFunction -): Promise => { - try { - const authHeader = - req.headers.authorization && req.headers.authorization.split(" ")[1]; - const token = authHeader && authHeader.split(" ")[1]; - - if (!token) { - res.locals.user = null; - next(); - return; - } - - const decoded = verifyAccessToken(token); - if (!decoded) { - res.locals.user = null; - next(); - return; - } - - const user = await prisma.user.findUnique({ - where: { id: decoded.userId }, - select: { - id: true, - email: true, - nickname: true, - image: true, - createdAt: true, - updatedAt: true, - }, - }); - - res.locals.user = user ? (user as UserResponse) : null; - next(); - } catch (error) { - console.error("Optional auth middleware error:", error); - res.locals.user = null; - next(); - } -}; diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts new file mode 100644 index 000000000..f031a9ba3 --- /dev/null +++ b/src/middlewares/auth.ts @@ -0,0 +1,36 @@ +import { Request, Response, NextFunction } from 'express'; +import { verifyAccessToken } from '../utils/jwt'; + +export interface AuthRequest extends Request { + userId?: number; +} + +export function authenticate(req: AuthRequest, res: Response, next: NextFunction) { + try { + const token = req.cookies['access-token']; + + if (!token) { + return res.status(401).json({ message: 'Unauthorized: No token provided' }); + } + + const { userId } = verifyAccessToken(token); + req.userId = userId; + next(); + } catch (error) { + return res.status(401).json({ message: 'Unauthorized: Invalid token' }); + } +} + +export function optionalAuthenticate(req: AuthRequest, res: Response, next: NextFunction) { + try { + const token = req.cookies['access-token']; + + if (token) { + const { userId } = verifyAccessToken(token); + req.userId = userId; + } + next(); + } catch (error) { + next(); + } +} diff --git a/src/middlewares/error-handler.middleware.js b/src/middlewares/error-handler.middleware.js deleted file mode 100644 index e269d5b2a..000000000 --- a/src/middlewares/error-handler.middleware.js +++ /dev/null @@ -1,20 +0,0 @@ -const errorHandler = (err, req, res, next) => { - console.error(err.stack); - - // Prisma-specific errors - if (err.code === 'P2025') { - return res.status(404).json({ message: 'Resource not found.' }); - } - - // General error handling - const statusCode = err.statusCode || 500; - const message = err.message || 'Internal Server Error'; - - res.status(statusCode).json({ - status: 'error', - statusCode, - message, - }); -}; - -module.exports = errorHandler; diff --git a/src/middlewares/upload.middleware.js b/src/middlewares/upload.middleware.js deleted file mode 100644 index a11d5cf9d..000000000 --- a/src/middlewares/upload.middleware.js +++ /dev/null @@ -1,24 +0,0 @@ -const multer = require('multer'); -const path = require('path'); -const fs = require('fs'); - -// Ensure uploads directory exists -const uploadDir = path.join(__dirname, '../../uploads'); -if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }); -} - -const storage = multer.diskStorage({ - destination: (req, file, cb) => { - cb(null, uploadDir); - }, - filename: (req, file, cb) => { - const ext = path.extname(file.originalname); - const filename = `${Date.now()}${ext}`; - cb(null, filename); - }, -}); - -const upload = multer({ storage }); - -module.exports = upload; diff --git a/src/middlewares/validation.middleware.js b/src/middlewares/validation.middleware.js deleted file mode 100644 index 01c4e16c3..000000000 --- a/src/middlewares/validation.middleware.js +++ /dev/null @@ -1,28 +0,0 @@ -const { body, validationResult } = require('express-validator'); - -const handleValidationErrors = (req, res, next) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - next(); -}; - -exports.validateProduct = [ - body('name').notEmpty().withMessage('Name is required'), - body('description').notEmpty().withMessage('Description is required'), - body('price').isInt({ gt: 0 }).withMessage('Price must be a positive integer'), - body('tags').isArray().withMessage('Tags must be an array'), - handleValidationErrors, -]; - -exports.validateArticle = [ - body('title').notEmpty().withMessage('Title is required'), - body('content').notEmpty().withMessage('Content is required'), - handleValidationErrors, -]; - -exports.validateComment = [ - body('content').notEmpty().withMessage('Content is required'), - handleValidationErrors, -]; diff --git a/src/repositories/articleRepository.ts b/src/repositories/articleRepository.ts new file mode 100644 index 000000000..9c5947f99 --- /dev/null +++ b/src/repositories/articleRepository.ts @@ -0,0 +1,79 @@ +import { prisma } from '../utils/prisma'; + +export async function createArticle(data: { + title: string; + content: string; + userId: number; +}) { + return prisma.article.create({ + data, + }); +} + +export async function getArticles(params: { + page?: number; + pageSize?: number; + keyword?: string; +}) { + const { page = 1, pageSize = 10, keyword } = params; + const skip = (page - 1) * pageSize; + + const where = keyword + ? { + OR: [ + { title: { contains: keyword } }, + { content: { contains: keyword } }, + ], + } + : {}; + + const [list, totalCount] = await Promise.all([ + prisma.article.findMany({ + where, + skip, + take: pageSize, + orderBy: { createdAt: 'desc' }, + include: { + user: { + select: { + id: true, + nickname: true, + }, + }, + }, + }), + prisma.article.count({ where }), + ]); + + return { list, totalCount }; +} + +export async function getArticleById(id: number) { + return prisma.article.findUnique({ + where: { id }, + include: { + user: { + select: { + id: true, + nickname: true, + }, + }, + }, + }); +} + +export async function updateArticle(id: number, data: { + title?: string; + content?: string; +}) { + return prisma.article.update({ + where: { id }, + data, + }); +} + +export async function deleteArticle(id: number) { + return prisma.article.delete({ + where: { id }, + }); +} diff --git a/src/repositories/authRepository.ts b/src/repositories/authRepository.ts new file mode 100644 index 000000000..d3ef384a9 --- /dev/null +++ b/src/repositories/authRepository.ts @@ -0,0 +1,37 @@ +import { prisma } from '../utils/prisma'; +import bcrypt from 'bcrypt'; + +export async function createUser(data: { + email: string; + password: string; + nickname: string; + image?: string; +}) { + const hashedPassword = await bcrypt.hash(data.password, 10); + + return prisma.user.create({ + data: { + ...data, + password: hashedPassword, + }, + select: { + id: true, + email: true, + nickname: true, + createdAt: true, + updatedAt: true, + }, + }); +} + +export async function findUserByEmail(email: string) { + return prisma.user.findUnique({ + where: { email }, + }); +} + +export async function findUserById(id: number) { + return prisma.user.findUnique({ + where: { id }, + }); +} diff --git a/src/repositories/post.repository.ts b/src/repositories/post.repository.ts deleted file mode 100644 index 70bc9e857..000000000 --- a/src/repositories/post.repository.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Post } from '@/types'; - -export class PostRepository { - private posts: Post[] = [ - { id: 1, title: 'First Post', content: 'This is the first post', userId: 1 }, - { id: 2, title: 'Second Post', content: 'This is the second post', userId: 2 }, - ]; - - // 모든 포스트 조회 - findAll(): Post[] { - return this.posts; - } - - // ID로 포스트 조회 - findById(id: number): Post | undefined { - return this.posts.find(post => post.id === id); - } - - // 사용자 ID로 포스트 조회 - findByUserId(userId: number): Post[] { - return this.posts.filter(post => post.userId === userId); - } - - // 포스트 생성 - create(postData: Omit): Post { - const newPost: Post = { - id: this.getNextId(), - ...postData - }; - this.posts.push(newPost); - return newPost; - } - - // 포스트 수정 - update(id: number, postData: Partial>): Post | null { - const postIndex = this.posts.findIndex(post => post.id === id); - if (postIndex === -1) { - return null; - } - - this.posts[postIndex] = { ...this.posts[postIndex], ...postData }; - return this.posts[postIndex]; - } - - // 포스트 삭제 - delete(id: number): boolean { - const postIndex = this.posts.findIndex(post => post.id === id); - if (postIndex === -1) { - return false; - } - - this.posts.splice(postIndex, 1); - return true; - } - - // 다음 ID 생성 - private getNextId(): number { - return this.posts.length > 0 - ? Math.max(...this.posts.map(post => post.id)) + 1 - : 1; - } -} \ No newline at end of file diff --git a/src/repositories/productRepository.ts b/src/repositories/productRepository.ts new file mode 100644 index 000000000..69fb61db9 --- /dev/null +++ b/src/repositories/productRepository.ts @@ -0,0 +1,85 @@ +import { prisma } from '../utils/prisma'; + +export async function createProduct(data: { + name: string; + description: string; + price: number; + tags: string[]; + userId: number; +}) { + return prisma.product.create({ + data, + }); +} + +export async function getProducts(params: { + page?: number; + pageSize?: number; + keyword?: string; + sortBy?: string; +}) { + const { page = 1, pageSize = 10, keyword, sortBy = 'recent' } = params; + const skip = (page - 1) * pageSize; + + const where = keyword + ? { + name: { + contains: keyword, + }, + } + : {}; + + const orderBy = sortBy === 'recent' ? { createdAt: 'desc' as const } : { createdAt: 'asc' as const }; + + const [list, totalCount] = await Promise.all([ + prisma.product.findMany({ + where, + skip, + take: pageSize, + orderBy, + include: { + user: { + select: { + id: true, + nickname: true, + }, + }, + }, + }), + prisma.product.count({ where }), + ]); + + return { list, totalCount }; +} + +export async function getProductById(id: number) { + return prisma.product.findUnique({ + where: { id }, + include: { + user: { + select: { + id: true, + nickname: true, + }, + }, + }, + }); +} + +export async function updateProduct(id: number, data: { + name?: string; + description?: string; + price?: number; + tags?: string[]; +}) { + return prisma.product.update({ + where: { id }, + data, + }); +} + +export async function deleteProduct(id: number) { + return prisma.product.delete({ + where: { id }, + }); +} diff --git a/src/repositories/user.repository.ts b/src/repositories/user.repository.ts deleted file mode 100644 index 35f23f9a2..000000000 --- a/src/repositories/user.repository.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { User } from '@/types'; - -export class UserRepository { - private users: User[] = [ - { id: 1, name: 'John Doe', email: 'john@example.com', age: 30 }, - { id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 25 }, - ]; - - // 모든 사용자 조회 - findAll(): User[] { - return this.users; - } - - // ID로 사용자 조회 - findById(id: number): User | undefined { - return this.users.find(user => user.id === id); - } - - // 이메일로 사용자 조회 - findByEmail(email: string): User | undefined { - return this.users.find(user => user.email === email); - } - - // 사용자 생성 - create(userData: Omit): User { - const newUser: User = { - id: this.getNextId(), - ...userData - }; - this.users.push(newUser); - return newUser; - } - - // 사용자 수정 - update(id: number, userData: Partial>): User | null { - const userIndex = this.users.findIndex(user => user.id === id); - if (userIndex === -1) { - return null; - } - - this.users[userIndex] = { ...this.users[userIndex], ...userData }; - return this.users[userIndex]; - } - - // 사용자 삭제 - delete(id: number): boolean { - const userIndex = this.users.findIndex(user => user.id === id); - if (userIndex === -1) { - return false; - } - - this.users.splice(userIndex, 1); - return true; - } - - // 다음 ID 생성 - private getNextId(): number { - return this.users.length > 0 - ? Math.max(...this.users.map(user => user.id)) + 1 - : 1; - } -} \ No newline at end of file diff --git a/src/routes/articleRoutes.test.ts b/src/routes/articleRoutes.test.ts new file mode 100644 index 000000000..7d05f2090 --- /dev/null +++ b/src/routes/articleRoutes.test.ts @@ -0,0 +1,234 @@ +import request from 'supertest'; +import app from '../app'; +import { prisma } from '../utils/prisma'; +import { clearDatabase } from '../utils/testUtils'; + +describe('Article API Integration Tests', () => { + beforeEach(async () => { + await clearDatabase(prisma); + }); + + afterAll(async () => { + await prisma.$disconnect(); + }); + + describe('Public Endpoints', () => { + describe('GET /articles', () => { + beforeEach(async () => { + const user = await prisma.user.create({ + data: { + email: 'author@test.com', + password: 'pass', + nickname: 'Author', + }, + }); + + for (let i = 1; i <= 12; i++) { + await prisma.article.create({ + data: { + title: `Article Title ${i}`, + content: `Content for article number ${i}`, + userId: user.id, + }, + }); + } + }); + + it('should fetch articles with default pagination', async () => { + const response = await request(app).get('/articles'); + + expect(response.status).toBe(200); + expect(response.body.list).toHaveLength(10); + expect(response.body.totalCount).toBe(12); + }); + + it('should support pagination with custom parameters', async () => { + const response = await request(app).get('/articles?page=2&pageSize=5'); + + expect(response.status).toBe(200); + expect(response.body.list).toHaveLength(5); + }); + + it('should search articles by keyword', async () => { + const response = await request(app).get('/articles?keyword=Article 5'); + + expect(response.status).toBe(200); + expect(response.body.list.length).toBeGreaterThan(0); + expect(response.body.list[0].title).toContain('5'); + }); + }); + + describe('GET /articles/:id', () => { + it('should return article when it exists', async () => { + const user = await prisma.user.create({ + data: { + email: 'writer@test.com', + password: 'pass', + nickname: 'Writer', + }, + }); + + const article = await prisma.article.create({ + data: { + title: 'Sample Article', + content: 'This is the content', + userId: user.id, + }, + }); + + const response = await request(app).get(`/articles/${article.id}`); + + expect(response.status).toBe(200); + expect(response.body.title).toBe('Sample Article'); + expect(response.body.content).toBe('This is the content'); + }); + + it('should return 404 for non-existent article', async () => { + const response = await request(app).get('/articles/88888'); + + expect(response.status).toBe(404); + }); + }); + }); + + describe('Protected Endpoints', () => { + describe('POST /articles', () => { + it('should deny unauthenticated user', async () => { + const response = await request(app) + .post('/articles') + .send({ + title: 'Unauthorized Article', + content: 'Should not be created', + }); + + expect(response.status).toBe(401); + }); + + it('should create article for authenticated user', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'poster@test.com', + password: 'pass123', + nickname: 'Poster', + }); + + await agent.post('/auth/login').send({ + email: 'poster@test.com', + password: 'pass123', + }); + + const articleData = { + title: 'My New Article', + content: 'Interesting content here', + }; + + const response = await agent.post('/articles').send(articleData); + + expect(response.status).toBe(201); + expect(response.body.title).toBe(articleData.title); + expect(response.body.content).toBe(articleData.content); + }); + }); + + describe('PATCH /articles/:id', () => { + it('should let owner update their article', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'editor@test.com', + password: 'pass123', + nickname: 'Editor', + }); + + await agent.post('/auth/login').send({ + email: 'editor@test.com', + password: 'pass123', + }); + + const createRes = await agent.post('/articles').send({ + title: 'Draft Title', + content: 'Draft content', + }); + + const articleId = createRes.body.id; + + const updateRes = await agent.patch(`/articles/${articleId}`).send({ + title: 'Final Title', + content: 'Final content', + }); + + expect(updateRes.status).toBe(200); + expect(updateRes.body.title).toBe('Final Title'); + }); + + it('should deny update from non-owner', async () => { + const author = await prisma.user.create({ + data: { + email: 'originalauthor@test.com', + password: 'pass', + nickname: 'Original Author', + }, + }); + + const article = await prisma.article.create({ + data: { + title: 'Original Title', + content: 'Original Content', + userId: author.id, + }, + }); + + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'hacker@test.com', + password: 'pass123', + nickname: 'Hacker', + }); + + await agent.post('/auth/login').send({ + email: 'hacker@test.com', + password: 'pass123', + }); + + const response = await agent.patch(`/articles/${article.id}`).send({ + title: 'Hacked Title', + }); + + expect(response.status).toBe(403); + }); + }); + + describe('DELETE /articles/:id', () => { + it('should allow owner to delete article', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'remover@test.com', + password: 'pass123', + nickname: 'Remover', + }); + + await agent.post('/auth/login').send({ + email: 'remover@test.com', + password: 'pass123', + }); + + const createRes = await agent.post('/articles').send({ + title: 'To Delete', + content: 'Will be removed', + }); + + const articleId = createRes.body.id; + + const deleteRes = await agent.delete(`/articles/${articleId}`); + + expect(deleteRes.status).toBe(200); + + const checkRes = await agent.get(`/articles/${articleId}`); + expect(checkRes.status).toBe(404); + }); + }); + }); +}); diff --git a/src/routes/articleRoutes.ts b/src/routes/articleRoutes.ts new file mode 100644 index 000000000..18b0afad1 --- /dev/null +++ b/src/routes/articleRoutes.ts @@ -0,0 +1,79 @@ +import express, { Response } from 'express'; +import * as articleService from '../services/articleService'; +import { authenticate, optionalAuthenticate, AuthRequest } from '../middlewares/auth'; + +const router = express.Router(); + +// Public routes +router.get('/', optionalAuthenticate, async (req: AuthRequest, res: Response) => { + try { + const { page, pageSize, keyword } = req.query; + const articles = await articleService.getArticles({ + page: page ? parseInt(page as string) : undefined, + pageSize: pageSize ? parseInt(pageSize as string) : undefined, + keyword: keyword as string, + }); + res.status(200).json(articles); + } catch (error: any) { + res.status(500).json({ message: error.message }); + } +}); + +router.get('/:id', optionalAuthenticate, async (req: AuthRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + const article = await articleService.getArticleById(id); + res.status(200).json(article); + } catch (error: any) { + res.status(404).json({ message: error.message }); + } +}); + +// Protected routes +router.post('/', authenticate, async (req: AuthRequest, res: Response) => { + try { + const { title, content } = req.body; + + if (!title || !content) { + return res.status(400).json({ message: 'Missing required fields' }); + } + + const article = await articleService.createArticle({ + title, + content, + userId: req.userId!, + }); + res.status(201).json(article); + } catch (error: any) { + res.status(400).json({ message: error.message }); + } +}); + +router.patch('/:id', authenticate, async (req: AuthRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + const { title, content } = req.body; + + const article = await articleService.updateArticle(id, req.userId!, { + title, + content, + }); + res.status(200).json(article); + } catch (error: any) { + const status = error.message.includes('not found') ? 404 : error.message.includes('not authorized') ? 403 : 400; + res.status(status).json({ message: error.message }); + } +}); + +router.delete('/:id', authenticate, async (req: AuthRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + await articleService.deleteArticle(id, req.userId!); + res.status(200).json({ message: 'Article deleted successfully' }); + } catch (error: any) { + const status = error.message.includes('not found') ? 404 : error.message.includes('not authorized') ? 403 : 400; + res.status(status).json({ message: error.message }); + } +}); + +export default router; diff --git a/src/routes/auth.ts b/src/routes/auth.ts deleted file mode 100644 index 14591b534..000000000 --- a/src/routes/auth.ts +++ /dev/null @@ -1,30 +0,0 @@ -import express from "express"; -import { - signup, - signupValidation, - login, - loginValidation, - refreshTokens, - logout, -} from "../controllers/authController"; - -const router = express.Router(); - -// express에서 제공하는 Request 타입과 직접 만드신 AuthenticatedRequest 타입이 충돌해서 생기는 문제... -// 이걸 해결하는 방법이 여러가지가 있음... -// 1. declare global을 사용해서 Express Request의 타입을 전역적으로 수정 및 확장 -> 저는 추천하지 않음. -// 2. Response의 타입에 있는 Locals 객체를 사용하는 방법이 있음. - -// 회원가입 -router.post("/signup", signupValidation, signup); - -// 로그인 -router.post("/login", loginValidation, login); - -// 토큰 갱신 -router.post("/refresh", refreshTokens); - -// 로그아웃 -router.post("/logout", logout); - -export default router; diff --git a/src/routes/authRoutes.test.ts b/src/routes/authRoutes.test.ts new file mode 100644 index 000000000..bd3f91a34 --- /dev/null +++ b/src/routes/authRoutes.test.ts @@ -0,0 +1,141 @@ +import request from 'supertest'; +import app from '../app'; +import { prisma } from '../utils/prisma'; +import { clearDatabase } from '../utils/testUtils'; + +describe('Auth API Integration Tests', () => { + beforeEach(async () => { + await clearDatabase(prisma); + }); + + afterAll(async () => { + await prisma.$disconnect(); + }); + + describe('POST /auth/register', () => { + it('should register a new user successfully', async () => { + const newUser = { + email: 'user@test.com', + password: 'testpass123', + nickname: 'TestNickname', + }; + + const response = await request(app) + .post('/auth/register') + .send(newUser); + + expect(response.status).toBe(201); + expect(response.body).toHaveProperty('id'); + expect(response.body.email).toBe(newUser.email); + expect(response.body.nickname).toBe(newUser.nickname); + expect(response.body).not.toHaveProperty('password'); + }); + + it('should return 400 when email already exists', async () => { + const userData = { + email: 'duplicate@test.com', + password: 'password123', + nickname: 'User One', + }; + + await request(app).post('/auth/register').send(userData); + + const response = await request(app) + .post('/auth/register') + .send(userData); + + expect(response.status).toBe(400); + expect(response.body.message).toContain('already exists'); + }); + + it('should return 400 when required fields are missing', async () => { + const invalidData = [ + { password: 'pass123', nickname: 'Test' }, + { email: 'test@example.com', nickname: 'Test' }, + { email: 'test@example.com', password: 'pass123' }, + ]; + + for (const data of invalidData) { + const response = await request(app) + .post('/auth/register') + .send(data); + + expect(response.status).toBe(400); + } + }); + }); + + describe('POST /auth/login', () => { + const testUser = { + email: 'login@test.com', + password: 'mypassword', + nickname: 'LoginUser', + }; + + beforeEach(async () => { + await request(app).post('/auth/register').send(testUser); + }); + + it('should login with correct credentials', async () => { + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: testUser.password, + }); + + expect(response.status).toBe(200); + expect(response.headers['set-cookie']).toBeDefined(); + + const cookies = response.headers['set-cookie']; + expect(cookies.some((cookie: string) => cookie.includes('access-token='))).toBe(true); + expect(cookies.some((cookie: string) => cookie.includes('refresh-token='))).toBe(true); + }); + + it('should return 400 with wrong password', async () => { + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: 'wrongpassword', + }); + + expect(response.status).toBe(400); + expect(response.body.message).toContain('Invalid'); + }); + + it('should return 400 with non-existent email', async () => { + const response = await request(app) + .post('/auth/login') + .send({ + email: 'notexist@test.com', + password: 'password123', + }); + + expect(response.status).toBe(400); + expect(response.body.message).toContain('Invalid'); + }); + }); + + describe('POST /auth/logout', () => { + it('should logout and clear cookies', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'logout@test.com', + password: 'pass123', + nickname: 'LogoutTest', + }); + + await agent.post('/auth/login').send({ + email: 'logout@test.com', + password: 'pass123', + }); + + const response = await agent.post('/auth/logout'); + + expect(response.status).toBe(200); + expect(response.body.message).toContain('Logout successful'); + }); + }); +}); diff --git a/src/routes/authRoutes.ts b/src/routes/authRoutes.ts new file mode 100644 index 000000000..a68090ca6 --- /dev/null +++ b/src/routes/authRoutes.ts @@ -0,0 +1,46 @@ +import express, { Request, Response } from 'express'; +import * as authService from '../services/authService'; + +const router = express.Router(); + +router.post('/register', async (req: Request, res: Response) => { + try { + const { email, password, nickname, image } = req.body; + + if (!email || !password || !nickname) { + return res.status(400).json({ message: 'Missing required fields' }); + } + + const user = await authService.register({ email, password, nickname, image }); + res.status(201).json(user); + } catch (error: any) { + res.status(400).json({ message: error.message }); + } +}); + +router.post('/login', async (req: Request, res: Response) => { + try { + const { email, password } = req.body; + + if (!email || !password) { + return res.status(400).json({ message: 'Missing required fields' }); + } + + const { accessToken, refreshToken } = await authService.login(email, password); + + res.cookie('access-token', accessToken, { httpOnly: true }); + res.cookie('refresh-token', refreshToken, { httpOnly: true }); + + res.status(200).json({ message: 'Login successful' }); + } catch (error: any) { + res.status(400).json({ message: error.message }); + } +}); + +router.post('/logout', (req: Request, res: Response) => { + res.clearCookie('access-token'); + res.clearCookie('refresh-token'); + res.status(200).json({ message: 'Logout successful' }); +}); + +export default router; diff --git a/src/routes/comments.ts b/src/routes/comments.ts deleted file mode 100644 index e04cef033..000000000 --- a/src/routes/comments.ts +++ /dev/null @@ -1,17 +0,0 @@ -import express from 'express'; -import { - updateComment, - deleteComment, - commentValidation -} from '../controllers/commentController'; -import { authenticateToken } from '../middleware/auth'; - -const router = express.Router(); - -// 댓글 수정 (작성자만) -router.put('/:id', authenticateToken, commentValidation, updateComment); - -// 댓글 삭제 (작성자만) -router.delete('/:id', authenticateToken, deleteComment); - -export default router; \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts deleted file mode 100644 index 8876ed646..000000000 --- a/src/routes/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Router } from 'express'; -import userRoutes from './user.routes'; -import postRoutes from './post.routes'; - -const router = Router(); - -// API 라우트 등록 -router.use('/users', userRoutes); -router.use('/posts', postRoutes); - -export default router; \ No newline at end of file diff --git a/src/routes/post.routes.ts b/src/routes/post.routes.ts deleted file mode 100644 index 8dcd1bcfc..000000000 --- a/src/routes/post.routes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Router } from 'express'; -import { PostController } from '@/controllers/post.controller'; - -const router = Router(); -const postController = new PostController(); - -// 포스트 관련 라우트 -router.get('/', postController.getAllPosts); -router.get('/:id', postController.getPostById); -router.get('/user/:userId', postController.getPostsByUserId); -router.post('/', postController.createPost); -router.put('/:id', postController.updatePost); -router.delete('/:id', postController.deletePost); - -export default router; \ No newline at end of file diff --git a/src/routes/posts.ts b/src/routes/posts.ts deleted file mode 100644 index cd16cbb8b..000000000 --- a/src/routes/posts.ts +++ /dev/null @@ -1,54 +0,0 @@ -import express from 'express'; -import { - getPosts, - getPost, - createPost, - updatePost, - deletePost, - postValidation -} from '../controllers/postController'; -import { - createPostComment, - getPostComments, - commentValidation -} from '../controllers/commentController'; -import { - togglePostLike, - getPostLikeStatus, - getPostLikes -} from '../controllers/likeController'; -import { authenticateToken, optionalAuth } from '../middleware/auth'; - -const router = express.Router(); - -// 게시글 목록 조회 (로그인 선택적) -router.get('/', optionalAuth, getPosts); - -// 게시글 상세 조회 (로그인 선택적) -router.get('/:id', optionalAuth, getPost); - -// 게시글 생성 (로그인 필수) -router.post('/', authenticateToken, postValidation, createPost); - -// 게시글 수정 (작성자만) -router.put('/:id', authenticateToken, postValidation, updatePost); - -// 게시글 삭제 (작성자만) -router.delete('/:id', authenticateToken, deletePost); - -// 게시글 댓글 목록 조회 -router.get('/:id/comments', getPostComments); - -// 게시글 댓글 생성 (로그인 필수) -router.post('/:id/comments', authenticateToken, commentValidation, createPostComment); - -// 게시글 좋아요/취소 토글 (로그인 필수) -router.post('/:id/like', authenticateToken, togglePostLike); - -// 게시글 좋아요 상태 조회 (로그인 필수) -router.get('/:id/like/status', authenticateToken, getPostLikeStatus); - -// 게시글 좋아요한 사용자 목록 조회 -router.get('/:id/likes', getPostLikes); - -export default router; \ No newline at end of file diff --git a/src/routes/productRoutes.test.ts b/src/routes/productRoutes.test.ts new file mode 100644 index 000000000..8ca224a85 --- /dev/null +++ b/src/routes/productRoutes.test.ts @@ -0,0 +1,259 @@ +import request from 'supertest'; +import app from '../app'; +import { prisma } from '../utils/prisma'; +import { clearDatabase } from '../utils/testUtils'; + +describe('Product API Integration Tests', () => { + beforeEach(async () => { + await clearDatabase(prisma); + }); + + afterAll(async () => { + await prisma.$disconnect(); + }); + + describe('Public Endpoints (No Authentication Required)', () => { + describe('GET /products', () => { + beforeEach(async () => { + const user = await prisma.user.create({ + data: { + email: 'productowner@test.com', + password: 'hashedpass', + nickname: 'Product Owner', + }, + }); + + for (let i = 1; i <= 15; i++) { + await prisma.product.create({ + data: { + name: `Product ${i}`, + description: `Description for product ${i}`, + price: 1000 * i, + tags: ['electronics', 'test'], + userId: user.id, + createdAt: new Date(Date.now() - i * 1000), + }, + }); + } + }); + + it('should retrieve all products with default pagination', async () => { + const response = await request(app).get('/products'); + + expect(response.status).toBe(200); + expect(response.body.list).toHaveLength(10); + expect(response.body.totalCount).toBe(15); + }); + + it('should support custom pagination parameters', async () => { + const response = await request(app).get('/products?page=2&pageSize=5'); + + expect(response.status).toBe(200); + expect(response.body.list).toHaveLength(5); + expect(response.body.list[0].name).toBe('Product 6'); + }); + + it('should filter products by keyword', async () => { + const response = await request(app).get('/products?keyword=Product 1'); + + expect(response.status).toBe(200); + expect(response.body.list.length).toBeGreaterThan(0); + expect(response.body.list.every((p: any) => p.name.includes('1'))).toBe(true); + }); + + it('should sort products by creation date', async () => { + const response = await request(app).get('/products?sortBy=recent'); + + expect(response.status).toBe(200); + expect(response.body.list[0].name).toBe('Product 1'); + }); + }); + + describe('GET /products/:id', () => { + it('should return product details when product exists', async () => { + const user = await prisma.user.create({ + data: { + email: 'owner@test.com', + password: 'pass', + nickname: 'Owner', + }, + }); + + const product = await prisma.product.create({ + data: { + name: 'Test Product', + description: 'A test product', + price: 5000, + tags: ['test'], + userId: user.id, + }, + }); + + const response = await request(app).get(`/products/${product.id}`); + + expect(response.status).toBe(200); + expect(response.body.name).toBe('Test Product'); + expect(response.body.price).toBe(5000); + }); + + it('should return 404 when product does not exist', async () => { + const response = await request(app).get('/products/99999'); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty('message'); + }); + }); + }); + + describe('Protected Endpoints (Authentication Required)', () => { + describe('POST /products', () => { + it('should return 401 for unauthenticated requests', async () => { + const response = await request(app) + .post('/products') + .send({ + name: 'Unauthorized Product', + description: 'Should fail', + price: 1000, + tags: ['test'], + }); + + expect(response.status).toBe(401); + }); + + it('should create product for authenticated user', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'creator@test.com', + password: 'pass123', + nickname: 'Creator', + }); + + await agent.post('/auth/login').send({ + email: 'creator@test.com', + password: 'pass123', + }); + + const productData = { + name: 'New Product', + description: 'Created by authenticated user', + price: 2000, + tags: ['new', 'test'], + }; + + const response = await agent.post('/products').send(productData); + + expect(response.status).toBe(201); + expect(response.body.name).toBe(productData.name); + expect(response.body.price).toBe(productData.price); + }); + }); + + describe('PATCH /products/:id', () => { + it('should allow owner to update their product', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'updater@test.com', + password: 'pass123', + nickname: 'Updater', + }); + + await agent.post('/auth/login').send({ + email: 'updater@test.com', + password: 'pass123', + }); + + const createRes = await agent.post('/products').send({ + name: 'Original Name', + description: 'Original Description', + price: 1000, + tags: ['original'], + }); + + const productId = createRes.body.id; + + const updateRes = await agent.patch(`/products/${productId}`).send({ + name: 'Updated Name', + price: 1500, + }); + + expect(updateRes.status).toBe(200); + expect(updateRes.body.name).toBe('Updated Name'); + expect(updateRes.body.price).toBe(1500); + }); + + it('should return 403 when non-owner tries to update', async () => { + const owner = await prisma.user.create({ + data: { + email: 'realowner@test.com', + password: 'pass', + nickname: 'Real Owner', + }, + }); + + const product = await prisma.product.create({ + data: { + name: 'Owners Product', + description: 'Description', + price: 3000, + tags: ['test'], + userId: owner.id, + }, + }); + + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'attacker@test.com', + password: 'pass123', + nickname: 'Attacker', + }); + + await agent.post('/auth/login').send({ + email: 'attacker@test.com', + password: 'pass123', + }); + + const response = await agent.patch(`/products/${product.id}`).send({ + name: 'Hacked Name', + }); + + expect(response.status).toBe(403); + }); + }); + + describe('DELETE /products/:id', () => { + it('should allow owner to delete their product', async () => { + const agent = request.agent(app); + + await agent.post('/auth/register').send({ + email: 'deleter@test.com', + password: 'pass123', + nickname: 'Deleter', + }); + + await agent.post('/auth/login').send({ + email: 'deleter@test.com', + password: 'pass123', + }); + + const createRes = await agent.post('/products').send({ + name: 'To Be Deleted', + description: 'Will be removed', + price: 500, + tags: ['delete'], + }); + + const productId = createRes.body.id; + + const deleteRes = await agent.delete(`/products/${productId}`); + + expect(deleteRes.status).toBe(200); + + const checkRes = await agent.get(`/products/${productId}`); + expect(checkRes.status).toBe(404); + }); + }); + }); +}); diff --git a/src/routes/productRoutes.ts b/src/routes/productRoutes.ts new file mode 100644 index 000000000..9b0127689 --- /dev/null +++ b/src/routes/productRoutes.ts @@ -0,0 +1,84 @@ +import express, { Response } from 'express'; +import * as productService from '../services/productService'; +import { authenticate, optionalAuthenticate, AuthRequest } from '../middlewares/auth'; + +const router = express.Router(); + +// Public routes +router.get('/', optionalAuthenticate, async (req: AuthRequest, res: Response) => { + try { + const { page, pageSize, keyword, sortBy } = req.query; + const products = await productService.getProducts({ + page: page ? parseInt(page as string) : undefined, + pageSize: pageSize ? parseInt(pageSize as string) : undefined, + keyword: keyword as string, + sortBy: sortBy as string, + }); + res.status(200).json(products); + } catch (error: any) { + res.status(500).json({ message: error.message }); + } +}); + +router.get('/:id', optionalAuthenticate, async (req: AuthRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + const product = await productService.getProductById(id); + res.status(200).json(product); + } catch (error: any) { + res.status(404).json({ message: error.message }); + } +}); + +// Protected routes +router.post('/', authenticate, async (req: AuthRequest, res: Response) => { + try { + const { name, description, price, tags } = req.body; + + if (!name || !description || price === undefined || !tags) { + return res.status(400).json({ message: 'Missing required fields' }); + } + + const product = await productService.createProduct({ + name, + description, + price, + tags, + userId: req.userId!, + }); + res.status(201).json(product); + } catch (error: any) { + res.status(400).json({ message: error.message }); + } +}); + +router.patch('/:id', authenticate, async (req: AuthRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + const { name, description, price, tags } = req.body; + + const product = await productService.updateProduct(id, req.userId!, { + name, + description, + price, + tags, + }); + res.status(200).json(product); + } catch (error: any) { + const status = error.message.includes('not found') ? 404 : error.message.includes('not authorized') ? 403 : 400; + res.status(status).json({ message: error.message }); + } +}); + +router.delete('/:id', authenticate, async (req: AuthRequest, res: Response) => { + try { + const id = parseInt(req.params.id); + await productService.deleteProduct(id, req.userId!); + res.status(200).json({ message: 'Product deleted successfully' }); + } catch (error: any) { + const status = error.message.includes('not found') ? 404 : error.message.includes('not authorized') ? 403 : 400; + res.status(status).json({ message: error.message }); + } +}); + +export default router; diff --git a/src/routes/products.ts b/src/routes/products.ts deleted file mode 100644 index 17b48f56d..000000000 --- a/src/routes/products.ts +++ /dev/null @@ -1,54 +0,0 @@ -import express from 'express'; -import { - getProducts, - getProduct, - createProduct, - updateProduct, - deleteProduct, - productValidation -} from '../controllers/productController'; -import { - createProductComment, - getProductComments, - commentValidation -} from '../controllers/commentController'; -import { - toggleProductLike, - getProductLikeStatus, - getProductLikes -} from '../controllers/likeController'; -import { authenticateToken, optionalAuth } from '../middleware/auth'; - -const router = express.Router(); - -// 상품 목록 조회 (로그인 선택적) -router.get('/', optionalAuth, getProducts); - -// 상품 상세 조회 (로그인 선택적) -router.get('/:id', optionalAuth, getProduct); - -// 상품 생성 (로그인 필수) -router.post('/', authenticateToken, productValidation, createProduct); - -// 상품 수정 (작성자만) -router.put('/:id', authenticateToken, productValidation, updateProduct); - -// 상품 삭제 (작성자만) -router.delete('/:id', authenticateToken, deleteProduct); - -// 상품 댓글 목록 조회 -router.get('/:id/comments', getProductComments); - -// 상품 댓글 생성 (로그인 필수) -router.post('/:id/comments', authenticateToken, commentValidation, createProductComment); - -// 상품 좋아요/취소 토글 (로그인 필수) -router.post('/:id/like', authenticateToken, toggleProductLike); - -// 상품 좋아요 상태 조회 (로그인 필수) -router.get('/:id/like/status', authenticateToken, getProductLikeStatus); - -// 상품 좋아요한 사용자 목록 조회 -router.get('/:id/likes', getProductLikes); - -export default router; \ No newline at end of file diff --git a/src/routes/user.routes.ts b/src/routes/user.routes.ts deleted file mode 100644 index ae9c7b04b..000000000 --- a/src/routes/user.routes.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Router } from 'express'; -import { UserController } from '@/controllers/user.controller'; - -const router = Router(); -const userController = new UserController(); - -// 사용자 관련 라우트 -router.get('/', userController.getAllUsers); -router.get('/:id', userController.getUserById); -router.post('/', userController.createUser); -router.put('/:id', userController.updateUser); -router.delete('/:id', userController.deleteUser); - -export default router; \ No newline at end of file diff --git a/src/routes/users.ts b/src/routes/users.ts deleted file mode 100644 index 53929f8f4..000000000 --- a/src/routes/users.ts +++ /dev/null @@ -1,35 +0,0 @@ -import express from "express"; -import { - getMyProfile, - updateMyProfile, - updateProfileValidation, - changePassword, - changePasswordValidation, - getMyProducts, - getMyLikedProducts, -} from "../controllers/userController"; -import { authenticateToken } from "../middleware/auth"; - -const router = express.Router(); - -// 내 정보 조회 -router.get("/me", authenticateToken, getMyProfile); - -// 내 정보 수정 -router.put("/me", authenticateToken, updateProfileValidation, updateMyProfile); - -// 비밀번호 변경 -router.put( - "/me/password", - authenticateToken, - changePasswordValidation, - changePassword -); - -// 내가 등록한 상품 목록 -router.get("/me/products", authenticateToken, getMyProducts); - -// 내가 좋아요한 상품 목록 -router.get("/me/liked-products", authenticateToken, getMyLikedProducts); - -export default router; diff --git a/src/services/articleService.ts b/src/services/articleService.ts new file mode 100644 index 000000000..9b3f2addc --- /dev/null +++ b/src/services/articleService.ts @@ -0,0 +1,56 @@ +import * as articleRepository from '../repositories/articleRepository'; + +export async function createArticle(data: { + title: string; + content: string; + userId: number; +}) { + return articleRepository.createArticle(data); +} + +export async function getArticles(params: { + page?: number; + pageSize?: number; + keyword?: string; +}) { + return articleRepository.getArticles(params); +} + +export async function getArticleById(id: number) { + const article = await articleRepository.getArticleById(id); + if (!article) { + throw new Error('Article not found'); + } + return article; +} + +export async function updateArticle( + id: number, + userId: number, + data: { + title?: string; + content?: string; + } +) { + const article = await articleRepository.getArticleById(id); + if (!article) { + throw new Error('Article not found'); + } + if (article.userId !== userId) { + throw new Error('You are not authorized to update this article'); + } + + return articleRepository.updateArticle(id, data); +} + +export async function deleteArticle(id: number, userId: number) { + const article = await articleRepository.getArticleById(id); + if (!article) { + throw new Error('Article not found'); + } + if (article.userId !== userId) { + throw new Error('You are not authorized to delete this article'); + } + + return articleRepository.deleteArticle(id); +} diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 000000000..86620c561 --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,35 @@ +import bcrypt from 'bcrypt'; +import * as authRepository from '../repositories/authRepository'; +import { generateAccessToken, generateRefreshToken } from '../utils/jwt'; + +export async function register(data: { + email: string; + password: string; + nickname: string; + image?: string; +}) { + const existingUser = await authRepository.findUserByEmail(data.email); + if (existingUser) { + throw new Error('Email already exists'); + } + + const user = await authRepository.createUser(data); + return user; +} + +export async function login(email: string, password: string) { + const user = await authRepository.findUserByEmail(email); + if (!user) { + throw new Error('Invalid email or password'); + } + + const isValid = await bcrypt.compare(password, user.password); + if (!isValid) { + throw new Error('Invalid email or password'); + } + + const accessToken = generateAccessToken(user.id); + const refreshToken = generateRefreshToken(user.id); + + return { accessToken, refreshToken, user }; +} diff --git a/src/services/post.service.ts b/src/services/post.service.ts deleted file mode 100644 index fd2a94a7a..000000000 --- a/src/services/post.service.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { PostRepository } from '@/repositories/post.repository'; -import { UserRepository } from '@/repositories/user.repository'; -import { CreatePostDto, UpdatePostDto, PostResponseDto, PostsResponseDto } from '@/dto/post.dto'; -import { Post } from '@/types'; - -export class PostService { - private postRepository: PostRepository; - private userRepository: UserRepository; - - constructor() { - this.postRepository = new PostRepository(); - this.userRepository = new UserRepository(); - } - - // 모든 포스트 조회 - async getAllPosts(): Promise { - const posts = this.postRepository.findAll(); - return { - posts: posts.map(this.mapToResponseDto), - total: posts.length - }; - } - - // ID로 포스트 조회 - async getPostById(id: number): Promise { - const post = this.postRepository.findById(id); - return post ? this.mapToResponseDto(post) : null; - } - - // 사용자 ID로 포스트 조회 - async getPostsByUserId(userId: number): Promise { - const posts = this.postRepository.findByUserId(userId); - return { - posts: posts.map(this.mapToResponseDto), - total: posts.length - }; - } - - // 포스트 생성 - async createPost(createPostDto: CreatePostDto): Promise { - // 사용자 존재 여부 확인 - const user = this.userRepository.findById(createPostDto.userId); - if (!user) { - throw new Error('존재하지 않는 사용자입니다.'); - } - - // 입력값 검증 - this.validatePostData(createPostDto); - - const post = this.postRepository.create(createPostDto); - return this.mapToResponseDto(post); - } - - // 포스트 수정 - async updatePost(id: number, updatePostDto: UpdatePostDto): Promise { - const existingPost = this.postRepository.findById(id); - if (!existingPost) { - return null; - } - - // 사용자 존재 여부 확인 (userId가 변경되는 경우) - if (updatePostDto.userId) { - const user = this.userRepository.findById(updatePostDto.userId); - if (!user) { - throw new Error('존재하지 않는 사용자입니다.'); - } - } - - // 입력값 검증 - this.validatePostUpdateData(updatePostDto); - - const updatedPost = this.postRepository.update(id, updatePostDto); - return updatedPost ? this.mapToResponseDto(updatedPost) : null; - } - - // 포스트 삭제 - async deletePost(id: number): Promise { - return this.postRepository.delete(id); - } - - // 포스트 데이터 검증 - private validatePostData(postData: CreatePostDto): void { - if (!postData.title?.trim()) { - throw new Error('제목은 필수 항목입니다.'); - } - - if (!postData.content?.trim()) { - throw new Error('내용은 필수 항목입니다.'); - } - - if (!postData.userId || postData.userId <= 0) { - throw new Error('유효하지 않은 사용자 ID입니다.'); - } - } - - // 포스트 수정 데이터 검증 - private validatePostUpdateData(postData: UpdatePostDto): void { - if (postData.title !== undefined && !postData.title?.trim()) { - throw new Error('제목은 빈 값일 수 없습니다.'); - } - - if (postData.content !== undefined && !postData.content?.trim()) { - throw new Error('내용은 빈 값일 수 없습니다.'); - } - - if (postData.userId !== undefined && (!postData.userId || postData.userId <= 0)) { - throw new Error('유효하지 않은 사용자 ID입니다.'); - } - } - - // Entity를 Response DTO로 변환 - private mapToResponseDto(post: Post): PostResponseDto { - return { - id: post.id, - title: post.title, - content: post.content, - userId: post.userId - }; - } -} \ No newline at end of file diff --git a/src/services/productService.test.ts b/src/services/productService.test.ts new file mode 100644 index 000000000..d019c6ed5 --- /dev/null +++ b/src/services/productService.test.ts @@ -0,0 +1,244 @@ +import * as productService from './productService'; +import * as productRepository from '../repositories/productRepository'; + +jest.mock('../repositories/productRepository'); + +describe('Product Service Unit Tests', () => { + const mockUser = { + id: 1, + email: 'test@example.com', + password: 'hashed', + nickname: 'TestUser', + createdAt: new Date(), + updatedAt: new Date(), + }; + + const mockProduct = { + id: 1, + name: 'Test Product', + description: 'Test Description', + price: 1000, + tags: ['electronics', 'test'], + userId: mockUser.id, + createdAt: new Date(), + updatedAt: new Date(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('createProduct', () => { + it('should successfully create a product', async () => { + jest.mocked(productRepository.createProduct).mockResolvedValue(mockProduct); + + const productData = { + name: mockProduct.name, + description: mockProduct.description, + price: mockProduct.price, + tags: mockProduct.tags, + userId: mockProduct.userId, + }; + + const result = await productService.createProduct(productData); + + expect(productRepository.createProduct).toHaveBeenCalledWith(productData); + expect(productRepository.createProduct).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockProduct); + }); + + it('should pass correct data to repository', async () => { + const spy = jest.spyOn(productRepository, 'createProduct').mockResolvedValue(mockProduct); + + const productData = { + name: 'New Product', + description: 'New Description', + price: 2000, + tags: ['new'], + userId: 2, + }; + + await productService.createProduct(productData); + + expect(spy).toHaveBeenCalledWith(productData); + expect(spy.mock.calls[0][0]).toMatchObject(productData); + + spy.mockRestore(); + }); + }); + + describe('getProductById', () => { + it('should return product when it exists', async () => { + const productWithUser = { + ...mockProduct, + user: { + id: mockUser.id, + nickname: mockUser.nickname, + }, + }; + + jest.mocked(productRepository.getProductById).mockResolvedValue(productWithUser); + + const result = await productService.getProductById(mockProduct.id); + + expect(productRepository.getProductById).toHaveBeenCalledWith(mockProduct.id); + expect(result).toEqual(productWithUser); + }); + + it('should throw error when product not found', async () => { + jest.mocked(productRepository.getProductById).mockResolvedValue(null); + + await expect(productService.getProductById(999)).rejects.toThrow('Product not found'); + expect(productRepository.getProductById).toHaveBeenCalledWith(999); + }); + }); + + describe('getProducts', () => { + it('should return list of products', async () => { + const mockList = { + list: [mockProduct], + totalCount: 1, + }; + + jest.mocked(productRepository.getProducts).mockResolvedValue(mockList); + + const params = { page: 1, pageSize: 10 }; + const result = await productService.getProducts(params); + + expect(productRepository.getProducts).toHaveBeenCalledWith(params); + expect(result).toEqual(mockList); + }); + + it('should pass filtering parameters correctly', async () => { + const spy = jest.spyOn(productRepository, 'getProducts').mockResolvedValue({ + list: [], + totalCount: 0, + }); + + const params = { + page: 2, + pageSize: 20, + keyword: 'laptop', + sortBy: 'recent', + }; + + await productService.getProducts(params); + + expect(spy).toHaveBeenCalledWith(params); + expect(spy.mock.calls[0][0]).toMatchObject(params); + + spy.mockRestore(); + }); + }); + + describe('updateProduct', () => { + it('should update product when user is owner', async () => { + const productWithUser = { + ...mockProduct, + user: { + id: mockUser.id, + nickname: mockUser.nickname, + }, + }; + + jest.mocked(productRepository.getProductById).mockResolvedValue(productWithUser); + jest.mocked(productRepository.updateProduct).mockResolvedValue({ + ...mockProduct, + name: 'Updated Name', + }); + + const updateData = { name: 'Updated Name' }; + const result = await productService.updateProduct(mockProduct.id, mockUser.id, updateData); + + expect(productRepository.getProductById).toHaveBeenCalledWith(mockProduct.id); + expect(productRepository.updateProduct).toHaveBeenCalledWith(mockProduct.id, updateData); + expect(result.name).toBe('Updated Name'); + }); + + it('should throw error when product not found', async () => { + jest.mocked(productRepository.getProductById).mockResolvedValue(null); + + await expect( + productService.updateProduct(999, mockUser.id, { name: 'Updated' }) + ).rejects.toThrow('Product not found'); + }); + + it('should throw error when user is not owner', async () => { + const productWithUser = { + ...mockProduct, + user: { + id: mockUser.id, + nickname: mockUser.nickname, + }, + }; + + jest.mocked(productRepository.getProductById).mockResolvedValue(productWithUser); + + const differentUserId = 999; + + await expect( + productService.updateProduct(mockProduct.id, differentUserId, { name: 'Hacked' }) + ).rejects.toThrow('You are not authorized to update this product'); + + expect(productRepository.updateProduct).not.toHaveBeenCalled(); + }); + }); + + describe('deleteProduct', () => { + it('should delete product when user is owner', async () => { + const productWithUser = { + ...mockProduct, + user: { + id: mockUser.id, + nickname: mockUser.nickname, + }, + }; + + jest.mocked(productRepository.getProductById).mockResolvedValue(productWithUser); + jest.mocked(productRepository.deleteProduct).mockResolvedValue(mockProduct); + + const result = await productService.deleteProduct(mockProduct.id, mockUser.id); + + expect(productRepository.getProductById).toHaveBeenCalledWith(mockProduct.id); + expect(productRepository.deleteProduct).toHaveBeenCalledWith(mockProduct.id); + expect(result).toEqual(mockProduct); + }); + + it('should throw error when user is not owner', async () => { + const productWithUser = { + ...mockProduct, + user: { + id: mockUser.id, + nickname: mockUser.nickname, + }, + }; + + jest.mocked(productRepository.getProductById).mockResolvedValue(productWithUser); + + const unauthorizedUserId = 777; + + await expect( + productService.deleteProduct(mockProduct.id, unauthorizedUserId) + ).rejects.toThrow('You are not authorized to delete this product'); + + expect(productRepository.deleteProduct).not.toHaveBeenCalled(); + }); + + it('should verify authorization before deletion using spy', async () => { + const getByIdSpy = jest.spyOn(productRepository, 'getProductById').mockResolvedValue({ + ...mockProduct, + user: { id: mockUser.id, nickname: mockUser.nickname }, + }); + + const deleteSpy = jest.spyOn(productRepository, 'deleteProduct').mockResolvedValue(mockProduct); + + await productService.deleteProduct(mockProduct.id, mockUser.id); + + expect(getByIdSpy).toHaveBeenCalledBefore(deleteSpy); + expect(deleteSpy).toHaveBeenCalledAfter(getByIdSpy); + + getByIdSpy.mockRestore(); + deleteSpy.mockRestore(); + }); + }); +}); diff --git a/src/services/productService.ts b/src/services/productService.ts new file mode 100644 index 000000000..7c41f3988 --- /dev/null +++ b/src/services/productService.ts @@ -0,0 +1,61 @@ +import * as productRepository from '../repositories/productRepository'; + +export async function createProduct(data: { + name: string; + description: string; + price: number; + tags: string[]; + userId: number; +}) { + return productRepository.createProduct(data); +} + +export async function getProducts(params: { + page?: number; + pageSize?: number; + keyword?: string; + sortBy?: string; +}) { + return productRepository.getProducts(params); +} + +export async function getProductById(id: number) { + const product = await productRepository.getProductById(id); + if (!product) { + throw new Error('Product not found'); + } + return product; +} + +export async function updateProduct( + id: number, + userId: number, + data: { + name?: string; + description?: string; + price?: number; + tags?: string[]; + } +) { + const product = await productRepository.getProductById(id); + if (!product) { + throw new Error('Product not found'); + } + if (product.userId !== userId) { + throw new Error('You are not authorized to update this product'); + } + + return productRepository.updateProduct(id, data); +} + +export async function deleteProduct(id: number, userId: number) { + const product = await productRepository.getProductById(id); + if (!product) { + throw new Error('Product not found'); + } + if (product.userId !== userId) { + throw new Error('You are not authorized to delete this product'); + } + + return productRepository.deleteProduct(id); +} diff --git a/src/services/user.service.ts b/src/services/user.service.ts deleted file mode 100644 index 6cf29ec9d..000000000 --- a/src/services/user.service.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { UserRepository } from '@/repositories/user.repository'; -import { CreateUserDto, UpdateUserDto, UserResponseDto, UsersResponseDto } from '@/dto/user.dto'; -import { User } from '@/types'; - -export class UserService { - private userRepository: UserRepository; - - constructor() { - this.userRepository = new UserRepository(); - } - - // 모든 사용자 조회 - async getAllUsers(): Promise { - const users = this.userRepository.findAll(); - return { - users: users.map(this.mapToResponseDto), - total: users.length - }; - } - - // ID로 사용자 조회 - async getUserById(id: number): Promise { - const user = this.userRepository.findById(id); - return user ? this.mapToResponseDto(user) : null; - } - - // 사용자 생성 - async createUser(createUserDto: CreateUserDto): Promise { - // 이메일 중복 검사 - const existingUser = this.userRepository.findByEmail(createUserDto.email); - if (existingUser) { - throw new Error('이미 존재하는 이메일입니다.'); - } - - // 입력값 검증 - this.validateUserData(createUserDto); - - const user = this.userRepository.create(createUserDto); - return this.mapToResponseDto(user); - } - - // 사용자 수정 - async updateUser(id: number, updateUserDto: UpdateUserDto): Promise { - const existingUser = this.userRepository.findById(id); - if (!existingUser) { - return null; - } - - // 이메일 중복 검사 (다른 사용자가 같은 이메일을 사용하는지) - if (updateUserDto.email) { - const userWithEmail = this.userRepository.findByEmail(updateUserDto.email); - if (userWithEmail && userWithEmail.id !== id) { - throw new Error('이미 존재하는 이메일입니다.'); - } - } - - // 입력값 검증 - this.validateUserUpdateData(updateUserDto); - - const updatedUser = this.userRepository.update(id, updateUserDto); - return updatedUser ? this.mapToResponseDto(updatedUser) : null; - } - - // 사용자 삭제 - async deleteUser(id: number): Promise { - return this.userRepository.delete(id); - } - - // 사용자 데이터 검증 - private validateUserData(userData: CreateUserDto): void { - if (!userData.name?.trim()) { - throw new Error('이름은 필수 항목입니다.'); - } - - if (!userData.email?.trim()) { - throw new Error('이메일은 필수 항목입니다.'); - } - - if (!this.isValidEmail(userData.email)) { - throw new Error('올바른 이메일 형식이 아닙니다.'); - } - - if (userData.age < 0 || userData.age > 150) { - throw new Error('유효하지 않은 나이입니다.'); - } - } - - // 사용자 수정 데이터 검증 - private validateUserUpdateData(userData: UpdateUserDto): void { - if (userData.name !== undefined && !userData.name?.trim()) { - throw new Error('이름은 빈 값일 수 없습니다.'); - } - - if (userData.email !== undefined) { - if (!userData.email?.trim()) { - throw new Error('이메일은 빈 값일 수 없습니다.'); - } - - if (!this.isValidEmail(userData.email)) { - throw new Error('올바른 이메일 형식이 아닙니다.'); - } - } - - if (userData.age !== undefined && (userData.age < 0 || userData.age > 150)) { - throw new Error('유효하지 않은 나이입니다.'); - } - } - - // 이메일 형식 검증 - private isValidEmail(email: string): boolean { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - } - - // Entity를 Response DTO로 변환 - private mapToResponseDto(user: User): UserResponseDto { - return { - id: user.id, - name: user.name, - email: user.email, - age: user.age - }; - } -} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 62ee7eba1..000000000 --- a/src/types/index.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { JwtPayload } from "jsonwebtoken"; - -// Refresh Token 타입 -export interface RefreshToken { - id: number; - token: string; - userId: number; - expiresAt: Date; - createdAt: Date; -} - -export interface PaginatedResponse { - data: T[]; - pagination: PaginationInfo; -} - -export interface PaginationInfo { - currentPage: number; - totalPages: number; - totalCount: number; - hasNext: boolean; -} - -// 상품 관련 타입 -export interface Product { - id: number; - title: string; - content: string; - image?: string; - price: number; - authorId: number; - createdAt: Date; - updatedAt: Date; - author?: UserResponse; - likesCount?: number; - commentsCount?: number; - isLiked?: boolean; -} - -// JWT 페이로드 타입 -export interface JwtTokenPayload extends JwtPayload { - userId: number; - type: "access" | "refresh"; -} - -// API 요청/응답 타입 -export interface AuthenticatedRequest extends Request { - user: UserResponse; -} - -export interface OptionalAuthRequest extends Request { - user?: UserResponse | null; -} - -// 회원가입 요청 타입 -export interface SignupRequest { - email: string; - nickname: string; - password: string; -} - -export interface UserResponse { - id: number; - email: string; - nickname: string; - image: string | null; - createdAt: Date; - updatedAt: Date; -} - -// API 응답 타입 -export interface ApiResponse { - message?: string; - data?: T; - error?: string; - details?: any; -} - -// IDE -> typescript language server 타입검사를 자동으로 해줌 -> 가끔씩 프로젝트가 커지거나 하면 잘 못잡고... -// typecheck전용ㅇ 스크립트를 하나 만들어두면 좋다. - -// 로그인 요청 타입 -export interface LoginRequest { - email: string; - password: string; -} - -// 토큰 응답 타입 -export interface TokenResponse { - message: string; - user: UserResponse; - accessToken: string; - refreshToken: string; -} - -// 토큰 갱신 요청 타입 -export interface RefreshTokenRequest { - refreshToken: string; -} - -// 프로필 업데이트 요청 타입 -export interface UpdateProfileRequest { - nickname?: string; - image?: string; -} - -// 비밀번호 변경 요청 타입 -export interface ChangePasswordRequest { - currentPassword: string; - newPassword: string; -} - -// 상품 생성/수정 요청 타입 -export interface ProductRequest { - title: string; - content: string; - price: number; - image?: string; -} - -// 사용자 엔티티 타입 -export interface User { - id: number; - name: string; - email: string; - age: number; -} - -// 포스트 엔티티 타입 -export interface Post { - id: number; - title: string; - content: string; - userId: number; -} - -// API 응답 타입 -export interface ApiResponse { - data?: T; - error?: string; - message?: string; -} - -// 페이지네이션 타입 -export interface PaginationQuery { - page?: number; - limit?: number; -} - -// 정렬 타입 -export interface SortQuery { - sortBy?: string; - sortOrder?: "asc" | "desc"; -} - -// 공통 쿼리 파라미터 타입 -export interface CommonQuery extends PaginationQuery, SortQuery { - search?: string; -} diff --git a/src/utils/auth.ts b/src/utils/auth.ts deleted file mode 100644 index 88cffd709..000000000 --- a/src/utils/auth.ts +++ /dev/null @@ -1,108 +0,0 @@ -import bcrypt from "bcryptjs"; -import jwt from "jsonwebtoken"; -import { PrismaClient } from "@prisma/client"; -import { JwtTokenPayload } from "../types"; - -const prisma = new PrismaClient(); - -// 비밀번호 해싱 -export const hashPassword = async (password: string): Promise => { - const saltRounds = 10; - return await bcrypt.hash(password, saltRounds); -}; - -// 비밀번호 검증 -export const comparePassword = async ( - password: string, - hashedPassword: string -): Promise => { - return await bcrypt.compare(password, hashedPassword); -}; - -// Access Token 생성 -export const generateAccessToken = (userId: number): string => { - const payload: Omit = { - userId, - type: "access", - }; - - return jwt.sign(payload, process.env.JWT_SECRET as string, { - expiresIn: "1H", - }); -}; - -// Refresh Token 생성 -export const generateRefreshToken = (userId: number): string => { - const payload: Omit = { - userId, - type: "refresh", - }; - - return jwt.sign(payload, process.env.JWT_REFRESH_SECRET as string, { - expiresIn: "7D", - }); -}; - -// Access Token 검증 -export const verifyAccessToken = (token: string): JwtTokenPayload | null => { - try { - return jwt.verify( - token, - process.env.JWT_SECRET as string - ) as JwtTokenPayload; - } catch (error) { - return null; - } -}; - -// Refresh Token 검증 -export const verifyRefreshToken = (token: string): JwtTokenPayload | null => { - try { - return jwt.verify( - token, - process.env.JWT_REFRESH_SECRET as string - ) as JwtTokenPayload; - } catch (error) { - return null; - } -}; - -// Refresh Token을 DB에 저장 -export const saveRefreshToken = async ( - userId: number, - token: string -): Promise => { - const expiresAt = new Date(); - expiresAt.setDate(expiresAt.getDate() + 7); // 7일 후 만료 - - await prisma.refreshToken.create({ - data: { - token, - userId, - expiresAt, - }, - }); -}; - -// Refresh Token 삭제 (로그아웃) -export const deleteRefreshToken = async (token: string): Promise => { - try { - await prisma.refreshToken.delete({ - where: { token }, - }); - } catch (error) { - // 토큰이 이미 없는 경우 무시 - console.log("Token not found or already deleted"); - } -}; - -// 만료된 Refresh Token 정리 -export const cleanupExpiredTokens = async (): Promise => { - await prisma.refreshToken.deleteMany({ - where: { - expiresAt: { - lt: new Date(), - }, - }, - }); -}; diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts new file mode 100644 index 000000000..1b784a5ca --- /dev/null +++ b/src/utils/jwt.ts @@ -0,0 +1,23 @@ +import jwt from 'jsonwebtoken'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET || 'default-access-secret'; +const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'default-refresh-secret'; + +export function generateAccessToken(userId: number): string { + return jwt.sign({ userId }, ACCESS_SECRET, { expiresIn: '1h' }); +} + +export function generateRefreshToken(userId: number): string { + return jwt.sign({ userId }, REFRESH_SECRET, { expiresIn: '7d' }); +} + +export function verifyAccessToken(token: string): { userId: number } { + return jwt.verify(token, ACCESS_SECRET) as { userId: number }; +} + +export function verifyRefreshToken(token: string): { userId: number } { + return jwt.verify(token, REFRESH_SECRET) as { userId: number }; +} diff --git a/src/utils/prisma.ts b/src/utils/prisma.ts index f3514dbfc..9b6c4ce30 100644 --- a/src/utils/prisma.ts +++ b/src/utils/prisma.ts @@ -1,11 +1,3 @@ import { PrismaClient } from '@prisma/client'; -// Prisma 클라이언트 싱글톤 인스턴스 -const prisma = new PrismaClient(); - -// Prisma 클라이언트 연결 종료 처리 -process.on('beforeExit', async () => { - await prisma.$disconnect(); -}); - -export default prisma; \ No newline at end of file +export const prisma = new PrismaClient(); diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts new file mode 100644 index 000000000..4ad1bbf8a --- /dev/null +++ b/src/utils/testUtils.ts @@ -0,0 +1,8 @@ +import { PrismaClient } from '@prisma/client'; + +export async function clearDatabase(prisma: PrismaClient) { + await prisma.comment.deleteMany(); + await prisma.product.deleteMany(); + await prisma.article.deleteMany(); + await prisma.user.deleteMany(); +} diff --git a/tsconfig.json b/tsconfig.json index cda5c67b8..1be42e9e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,35 +1,36 @@ { "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "lib": ["es2020"], - "outDir": "./dist", + // File Layout "rootDir": "./src", - "strict": true, + "outDir": "./dist", + + // Environment Settings + "module": "commonjs", + "target": "ES2020", + "lib": ["ES2020"], + "types": ["node", "jest"], + + // Module Resolution + "moduleResolution": "node", "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, + + // Other Outputs + "sourceMap": true, "declaration": true, "declarationMap": true, - "sourceMap": true, - "removeComments": false, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, + + // Stricter Typechecking Options + "strict": true, "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "moduleResolution": "node", - "baseUrl": "./src", - "paths": { - "@/*": ["*"], - "@/controllers/*": ["controllers/*"], - "@/services/*": ["services/*"], - "@/repositories/*": ["repositories/*"], - "@/types/*": ["types/*"], - "@/dto/*": ["dto/*"] - } + + // Other Options + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} \ No newline at end of file + "exclude": ["node_modules", "dist", "**/*.test.ts"] +}